Skip to content

Commit d30e4a0

Browse files
committed
chore: big refacotr of config resolve
1 parent 703c2f9 commit d30e4a0

File tree

19 files changed

+504
-615
lines changed

19 files changed

+504
-615
lines changed

packages/cli/src/__tests__/commands/lint.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ describe('handleLint', () => {
5555
logger: {
5656
info: vi.fn(),
5757
warn: vi.fn(),
58+
error: vi.fn(),
5859
output: vi.fn(),
5960
},
6061
formatProblems: vi.fn(),

packages/cli/src/__tests__/fixtures/config.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,5 @@ export const configFixture: Config = {
5454
getDecoratorSettings: vi.fn(),
5555
getUnusedRules: vi.fn(),
5656
getRulesForSpecVersion: vi.fn(),
57-
extendPaths: [],
58-
pluginPaths: [],
5957
forAlias: vi.fn(() => configFixture),
6058
} as Omit<Config, '_usedRules' | '_usedVersions'> as Config;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export default function localPlugin() {
2+
return {
3+
id: 'local',
4+
configs: {
5+
all: {},
6+
},
7+
assertions: {
8+
checkWordsCount: () => false,
9+
},
10+
};
11+
}

packages/core/src/__tests__/lint.test.ts

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -357,19 +357,21 @@ describe('lint', () => {
357357
showConsole: true # Not expected anymore
358358
layout: wrong-option
359359
`;
360-
const document = parseYamlToDocument(testConfigContent, '');
361-
const config = await createConfig({}, { document });
360+
const cwd = path.join(__dirname, 'fixtures');
361+
const config = await createConfig(testConfigContent, {
362+
configPath: path.join(cwd, 'redocly.yaml'),
363+
});
362364
const results = await lintConfig({ config });
363365

364-
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
366+
expect(replaceSourceWithRef(results, cwd)).toMatchInlineSnapshot(`
365367
[
366368
{
367369
"from": undefined,
368370
"location": [
369371
{
370372
"pointer": "#/apis",
371373
"reportOnKey": false,
372-
"source": "",
374+
"source": "redocly.yaml",
373375
},
374376
],
375377
"message": "Expected type \`ConfigApis\` (object) but got \`string\`",
@@ -383,7 +385,7 @@ describe('lint', () => {
383385
{
384386
"pointer": "#/theme/openapi/layout",
385387
"reportOnKey": false,
386-
"source": "",
388+
"source": "redocly.yaml",
387389
},
388390
],
389391
"message": "\`layout\` can be one of the following only: "stacked", "three-panel".",
@@ -403,8 +405,7 @@ describe('lint', () => {
403405
rules:
404406
operation-2xx-response: warn
405407
`;
406-
const document = parseYamlToDocument(testConfigContent, '');
407-
const config = await createConfig(testConfigContent, { document });
408+
const config = await createConfig(testConfigContent);
408409
const results = await lintConfig({ config });
409410

410411
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
@@ -442,19 +443,21 @@ describe('lint', () => {
442443
plugins:
443444
- './local-plugin.js'
444445
`;
445-
const document = parseYamlToDocument(testConfigContent, '');
446-
const config = await createConfig({}, { document });
446+
const cwd = path.join(__dirname, 'fixtures');
447+
const config = await createConfig(testConfigContent, {
448+
configPath: path.join(cwd, 'redocly.yaml'),
449+
});
447450
const results = await lintConfig({ config });
448451

449-
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
452+
expect(replaceSourceWithRef(results, cwd)).toMatchInlineSnapshot(`
450453
[
451454
{
452455
"from": undefined,
453456
"location": [
454457
{
455458
"pointer": "#/apis/main/plugins",
456459
"reportOnKey": true,
457-
"source": "",
460+
"source": "redocly.yaml",
458461
},
459462
],
460463
"message": "Property \`plugins\` is not expected here.",
@@ -467,8 +470,7 @@ describe('lint', () => {
467470
});
468471

469472
it('lintConfig should detect wrong fields in the default configuration after merging with the portal config schema', async () => {
470-
const document = parseYamlToDocument(testPortalConfigContent, '');
471-
const config = await createConfig(testPortalConfigContent, { document });
473+
const config = await createConfig(testPortalConfigContent);
472474
const results = await lintConfig({ config });
473475

474476
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
@@ -1071,17 +1073,13 @@ describe('lint', () => {
10711073
});
10721074

10731075
it('lintConfig should alternate its behavior when supplied externalConfigTypes', async () => {
1074-
const document = parseYamlToDocument(testPortalConfigContent, '');
1075-
const config = await createConfig(testPortalConfigContent, { document });
1076+
const config = await createConfig(testPortalConfigContent);
10761077
const results = await lintConfig({
1077-
externalConfigTypes: createConfigTypes(
1078-
{
1079-
type: 'object',
1080-
properties: { theme: rootRedoclyConfigSchema.properties.theme },
1081-
additionalProperties: false,
1082-
},
1083-
config
1084-
),
1078+
externalConfigTypes: createConfigTypes({
1079+
type: 'object',
1080+
properties: { theme: rootRedoclyConfigSchema.properties.theme },
1081+
additionalProperties: false,
1082+
}),
10851083
config,
10861084
});
10871085

packages/core/src/bundle.ts

Lines changed: 105 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,14 @@ import {
1212
import { isAbsoluteUrl, isExternalValue, isRef, refBaseName } from './ref-utils.js';
1313
import { initRules } from './config/rules.js';
1414
import { reportUnresolvedRef } from './rules/no-unresolved-refs.js';
15-
import { dequal, isEmptyObject, isPlainObject, isTruthy } from './utils.js';
15+
import { dequal, isPlainObject, isTruthy } from './utils.js';
1616
import { RemoveUnusedComponents as RemoveUnusedComponentsOas2 } from './decorators/oas2/remove-unused-components.js';
1717
import { RemoveUnusedComponents as RemoveUnusedComponentsOas3 } from './decorators/oas3/remove-unused-components.js';
1818
import { NormalizedConfigTypes } from './types/redocly-yaml.js';
19-
import {
20-
mergeExtends,
21-
resolvePreset,
22-
type ResolvedGovernanceConfig,
23-
type Config,
24-
} from './config/index.js';
19+
import { mergeExtends, resolvePreset, type Config } from './config/index.js';
2520
import path from 'path';
26-
import { defaultPlugin } from './config/builtIn.js';
2721

22+
import type { Plugin } from './config/types.js';
2823
import type { Location } from './ref-utils.js';
2924
import type { Oas3Visitor, Oas2Visitor } from './visitors.js';
3025
import type { NormalizedNodeType, NodeType } from './types/index.js';
@@ -47,15 +42,15 @@ export type CoreBundleOptions = {
4742
keepUrlRefs?: boolean;
4843
};
4944

50-
function bundleExtends(node: any, ctx: UserContext) {
45+
function bundleExtends({ node, ctx, plugins }: { node: any; ctx: UserContext; plugins: Plugin[] }) {
5146
if (!node.extends) {
5247
return node;
5348
}
5449

55-
const resolvedExtends = node.extends
50+
const resolvedExtends = (node.extends || [])
5651
.map((presetItem: string) => {
5752
if (!isAbsoluteUrl(presetItem) && !path.extname(presetItem)) {
58-
return resolvePreset(presetItem, [defaultPlugin]); // TODO: implement plugins
53+
return resolvePreset(presetItem, plugins);
5954
}
6055

6156
const resolvedRef = ctx.resolve({ $ref: presetItem });
@@ -66,83 +61,139 @@ function bundleExtends(node: any, ctx: UserContext) {
6661
})
6762
.filter(isTruthy);
6863

69-
return removeEmptyRules(
70-
mergeExtends([
71-
...resolvedExtends.map((nested: any) => bundleExtends(nested, ctx)),
72-
{ ...node, extends: undefined },
73-
])
74-
);
64+
return mergeExtends([
65+
...resolvedExtends.map((nested: any) => bundleExtends({ node: nested, ctx, plugins })),
66+
{ ...node, extends: undefined },
67+
]);
7568
}
7669

77-
const bundleVisitor = () => {
78-
const collectedPlugins: string[] = [];
70+
function configBundleVisitor(plugins: Plugin[]) {
71+
// let rootConfig: RawUniversalConfig | undefined;
72+
73+
return normalizeVisitors(
74+
[
75+
{
76+
severity: 'error',
77+
ruleId: 'configBundler',
78+
visitor: {
79+
ref: {
80+
leave(node: OasRef, ctx: UserContext, resolved: ResolveResult<any>) {
81+
replaceRef(node, resolved, ctx);
82+
},
83+
},
84+
ConfigGovernance: {
85+
leave(node: any, ctx: UserContext) {
86+
handleNode(node, ctx);
87+
},
88+
},
89+
ConfigApisProperties: {
90+
leave(node: any, ctx: UserContext) {
91+
// ignore extends from root config if defined in the current node
92+
handleNode(node, ctx);
93+
},
94+
},
95+
'rootRedoclyConfigSchema.scorecard.levels_items': {
96+
leave(node: any, ctx: UserContext) {
97+
handleNode(node, ctx);
98+
},
99+
},
100+
ConfigRoot: {
101+
leave(node: any, ctx: UserContext) {
102+
if (node.extends) {
103+
const bundled = bundleExtends({ node, ctx, plugins });
104+
Object.assign(node, bundled);
105+
delete node.extends;
106+
}
107+
},
108+
},
109+
},
110+
},
111+
],
112+
NormalizedConfigTypes
113+
);
79114

80115
function handleNode(node: any, ctx: UserContext) {
81116
if (node.extends) {
82-
const bundled = bundleExtends(node, ctx);
117+
const bundled = bundleExtends({ node, ctx, plugins });
83118
Object.assign(node, bundled);
84119
delete node.extends;
85-
collectedPlugins.push(...(bundled.plugins as unknown as any[]));
86-
delete bundled.plugins;
87120
}
88121
}
122+
}
89123

124+
function collectPluginsVisitor() {
125+
const plugins: (string | Plugin)[] = [];
90126
return normalizeVisitors(
91127
[
92128
{
93129
severity: 'error',
94130
ruleId: 'configBundler',
95131
visitor: {
96-
ref: {
97-
leave(node: OasRef, ctx: UserContext, resolved: ResolveResult<any>) {
98-
replaceRef(node, resolved, ctx);
99-
},
100-
},
132+
ref: {},
101133
ConfigGovernance: {
102134
leave(node: any, ctx: UserContext) {
103135
handleNode(node, ctx);
104136
},
105137
},
106-
ConfigStyleguideList: {
138+
ConfigApisProperties: {
107139
leave(node: any, ctx: UserContext) {
108140
handleNode(node, ctx);
109141
},
110-
'rootRedoclyConfigSchema.scorecard.levels_items': {
111-
leave(node: any, ctx: UserContext) {
112-
handleNode(node, ctx);
113-
},
142+
},
143+
'rootRedoclyConfigSchema.scorecard.levels_items': {
144+
leave(node: any, ctx: UserContext) {
145+
handleNode(node, ctx);
114146
},
115-
116-
ConfigRoot: {
117-
leave(node: any, ctx: UserContext) {
118-
if (node.extends) {
119-
const bundled = bundleExtends(node, ctx);
120-
Object.assign(node, bundled);
121-
node.plugins = Array.from(
122-
new Set([...(node.plugins || []), ...collectedPlugins])
123-
);
124-
delete node.extends;
125-
}
126-
},
147+
},
148+
ConfigRoot: {
149+
leave(node: any) {
150+
if ((Array.isArray(node.plugins) && node.plugins.length > 0) || plugins.length > 0) {
151+
node.plugins = [...(node.plugins || []), ...plugins];
152+
}
127153
},
128154
},
129155
},
130156
},
131157
],
132158
NormalizedConfigTypes
133159
);
134-
};
135160

136-
function removeEmptyRules(config: ResolvedGovernanceConfig) {
137-
// TODO: convert strings to constants
138-
return Object.fromEntries(
139-
Object.entries(config).filter(
140-
([key, value]) => !isEmptyObject(value) && key !== 'pluginPaths' && key !== 'extendPaths'
141-
)
142-
);
161+
function handleNode(node: any, ctx: UserContext) {
162+
if (Array.isArray(node.plugins)) {
163+
plugins.push(
164+
...node.plugins.map((p: string) =>
165+
typeof p === 'string' ? path.resolve(path.dirname(ctx.location.source.absoluteRef), p) : p
166+
)
167+
);
168+
delete node.plugins;
169+
}
170+
}
143171
}
144172

145-
export async function bundleConfig(document: Document, resolvedRefMap: ResolvedRefMap) {
173+
export function collectConfigPlugins(document: Document, resolvedRefMap: ResolvedRefMap) {
174+
const ctx: BundleContext = {
175+
problems: [],
176+
oasVersion: SpecVersion.OAS3_0,
177+
refTypes: new Map<string, NormalizedNodeType>(),
178+
visitorsData: {},
179+
};
180+
181+
walkDocument({
182+
document,
183+
rootType: NormalizedConfigTypes.ConfigRoot,
184+
normalizedVisitors: collectPluginsVisitor(),
185+
resolvedRefMap,
186+
ctx,
187+
});
188+
189+
return document.parsed ?? {};
190+
}
191+
192+
export function bundleConfig(
193+
document: Document,
194+
resolvedRefMap: ResolvedRefMap,
195+
plugins: Plugin[]
196+
) {
146197
const ctx: BundleContext = {
147198
problems: [],
148199
oasVersion: SpecVersion.OAS3_0,
@@ -153,7 +204,7 @@ export async function bundleConfig(document: Document, resolvedRefMap: ResolvedR
153204
walkDocument({
154205
document,
155206
rootType: NormalizedConfigTypes.ConfigRoot,
156-
normalizedVisitors: bundleVisitor(),
207+
normalizedVisitors: configBundleVisitor(plugins),
157208
resolvedRefMap,
158209
ctx,
159210
});

0 commit comments

Comments
 (0)