Skip to content

Commit

Permalink
build(rulesets): abandon props mangling
Browse files Browse the repository at this point in the history
  • Loading branch information
P0lip committed Jan 12, 2024
1 parent d51690b commit e6c5ab9
Show file tree
Hide file tree
Showing 10 changed files with 153 additions and 589 deletions.
2 changes: 1 addition & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
**/__fixtures__/**
/test-harness/tests/
/packages/*/dist
/packages/rulesets/src/oas/schemas/compiled.ts
/packages/rulesets/src/oas/schemas/validators.ts
/packages/*/CHANGELOG.md
packages/formatters/src/html/templates.ts
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ node_modules
!.yarn/versions

packages/formatters/src/html/templates.ts
packages/rulesets/src/oas/schemas/compiled.ts
packages/rulesets/src/oas/schemas/validators.ts
packages/cli/binaries
packages/*/src/version.ts
/test-harness/tmp/
Expand Down
6 changes: 2 additions & 4 deletions packages/rulesets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,15 @@
"tslib": "^2.3.0"
},
"devDependencies": {
"@rollup/plugin-sucrase": "^5.0.2",
"@rollup/plugin-terser": "^0.4.4",
"@stoplight/path": "^1.3.2",
"@stoplight/spectral-parsers": "*",
"@stoplight/spectral-ref-resolver": "*",
"gzip-size": "^6.0.0",
"immer": "^9.0.6"
"immer": "^9.0.6",
"terser": "^5.26.0"
},
"scripts": {
"compile-schemas": "ts-node -T ./scripts/compile-schemas.ts",
"postbuild": "ts-node -T ./scripts/bundle.ts",
"prelint": "yarn compile-schemas --quiet",
"pretest": "yarn compile-schemas --quiet",
"prebuild": "yarn compile-schemas --quiet"
Expand Down
37 changes: 0 additions & 37 deletions packages/rulesets/scripts/bundle.ts

This file was deleted.

28 changes: 24 additions & 4 deletions packages/rulesets/scripts/compile-schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import standaloneCode from 'ajv/dist/standalone/index.js';
import ajvErrors from 'ajv-errors';
import ajvFormats from 'ajv-formats';
import chalk from 'chalk';
import { minify } from 'terser';
import { sync } from 'gzip-size';

const cwd = path.join(__dirname, '../src');

Expand Down Expand Up @@ -39,24 +41,42 @@ Promise.all(schemas)
code: {
esm: true,
source: true,
optimize: 1,
},
});

ajvFormats(ajv);
ajvErrors(ajv);

const target = path.join(cwd, 'oas/schemas/compiled.ts');
const target = path.join(cwd, 'oas/schemas/validators.ts');
const basename = path.basename(target);
const code = standaloneCode(ajv, {
oas2_0: 'http://swagger.io/v2/schema.json',
oas3_0: 'https://spec.openapis.org/oas/3.0/schema/2019-04-02',
oas3_1: 'https://spec.openapis.org/oas/3.1/schema/2021-09-28',
});

log('writing %s size is %dKB', path.join(target, '..', basename), Math.round((code.length / 1024) * 100) / 100);
const minified = (
await minify(code, {
compress: {
passes: 2,
},
ecma: 2020,
module: true,
format: {
comments: false,
},
})
).code as string;

await fs.promises.writeFile(path.join(target, '..', basename), ['// @ts-nocheck', code].join('\n'));
log(
'writing %s size is %dKB (original), %dKB (minified) %dKB (minified + gzipped)',
path.join(target, '..', basename),
Math.round((code.length / 1024) * 100) / 100,
Math.round((minified.length / 1024) * 100) / 100,
Math.round((sync(minified) / 1024) * 100) / 100,
);

await fs.promises.writeFile(path.join(target, '..', basename), ['// @ts-nocheck', minified].join('\n'));
})
.then(() => {
log(chalk.green('Validators generated.'));
Expand Down
116 changes: 0 additions & 116 deletions packages/rulesets/src/oas/functions/_oasDocumentSchema.ts

This file was deleted.

108 changes: 105 additions & 3 deletions packages/rulesets/src/oas/functions/oasDocumentSchema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { createRulesetFunction } from '@stoplight/spectral-core';
import type { IFunctionResult } from '@stoplight/spectral-core';
import { oas2, oas3_1 } from '@stoplight/spectral-formats';
import _oasDocumentSchema from './_oasDocumentSchema';
import { isPlainObject, resolveInlineRef } from '@stoplight/json';
import type { ErrorObject } from 'ajv';
import leven from 'leven';

import * as validators from '../schemas/validators';

export default createRulesetFunction<unknown, null>(
{
Expand All @@ -11,8 +16,105 @@ export default createRulesetFunction<unknown, null>(
const formats = context.document.formats;
if (formats === null || formats === void 0) return;

const format = formats.has(oas2) ? 'oas2_0' : formats.has(oas3_1) ? 'oas3_1' : 'oas3_0';
const validator = formats.has(oas2)
? validators.oas2_0
: formats.has(oas3_1)
? validators.oas3_1
: validators.oas3_0;

validator(input);

const errors = validator['errors'] as ErrorObject[] | undefined;

return _oasDocumentSchema(format, input);
return errors?.filter(isRelevantError).map(e => processError(input, e));
},
);

function isRelevantError(error: ErrorObject): boolean {
return error.keyword !== 'if';
}

function processError(input: unknown, error: ErrorObject): IFunctionResult {
const path = error.instancePath === '' ? [] : error.instancePath.slice(1).split('/');
const property = path.length === 0 ? null : path[path.length - 1];

switch (error.keyword) {
case 'additionalProperties': {
const additionalProperty = error.params['additionalProperty'] as string;
path.push(additionalProperty);

return {
message: `Property "${additionalProperty}" is not expected to be here`,
path,
};
}

case 'enum': {
const allowedValues = error.params['allowedValues'] as unknown[];
const printedValues = allowedValues.map(value => JSON.stringify(value)).join(', ');
let suggestion: string;

if (!isPlainObject(input)) {
suggestion = '';
} else {
const value = resolveInlineRef(input, `#${error.instancePath}`);
if (typeof value !== 'string') {
suggestion = '';
} else {
const bestMatch = findBestMatch(value, allowedValues);

if (bestMatch !== null) {
suggestion = `. Did you mean "${bestMatch}"?`;
} else {
suggestion = '';
}
}
}

return {
message: `${cleanAjvMessage(property, error.message)}: ${printedValues}${suggestion}`,
path,
};
}

case 'errorMessage':
return {
message: String(error.message),
path,
};

default:
return {
message: cleanAjvMessage(property, error.message),
path,
};
}
}

function findBestMatch(value: string, allowedValues: unknown[]): string | null {
const matches = allowedValues
.filter<string>((value): value is string => typeof value === 'string')
.map(allowedValue => ({
value: allowedValue,
weight: leven(value, allowedValue),
}))
.sort((x, y) => (x.weight > y.weight ? 1 : x.weight < y.weight ? -1 : 0));

if (matches.length === 0) {
return null;
}

const bestMatch = matches[0];

return allowedValues.length === 1 || bestMatch.weight < bestMatch.value.length ? bestMatch.value : null;
}

const QUOTES = /['"]/g;
const NOT = /NOT/g;

function cleanAjvMessage(prop: string | null, message: string | undefined): string {
if (typeof message !== 'string') return '';

const cleanedMessage = message.replace(QUOTES, '"').replace(NOT, 'not');
return prop === null ? cleanedMessage : `"${prop}" property ${cleanedMessage}`;
}
Empty file.
File renamed without changes.
Loading

0 comments on commit e6c5ab9

Please sign in to comment.