generate runtime code with special-lint-fix

verify runtime code in CI
This commit is contained in:
Tobias Koppers 2020-08-05 08:41:50 +02:00
parent f3688dfb57
commit 6e71413a3e
4 changed files with 135 additions and 228 deletions

View File

@ -7,15 +7,17 @@
/** @typedef {(string|number|undefined|[])[]} SemVerRange */
const splitAndConvert = str => {
return str.split(".").map(item => (`${+item}` === item ? +item : item));
};
/**
* @param {string} str version string
* @returns {(string|number|undefined|[])[]} parsed version
*/
const parseVersion = str => {
var splitAndConvert = function (str) {
return str.split(".").map(function (item) {
// eslint-disable-next-line eqeqeq
return +item == item ? +item : item;
});
};
var match = /^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(str);
/** @type {(string|number|undefined|[])[]} */
var ver = match[1] ? splitAndConvert(match[1]) : [];
@ -31,21 +33,6 @@ const parseVersion = str => {
};
exports.parseVersion = parseVersion;
// must be a minimized version of above
exports.parseVersionRuntimeCode = runtimeTemplate =>
`var parseVersion = ${runtimeTemplate.basicFunction("str", [
`var convertNumber = ${runtimeTemplate.returningFunction(
"+str == str ? +str : str;",
"str"
)};`,
`var splitAndConvert = ${runtimeTemplate.returningFunction(
'str.split(".").map(convertNumber);',
"str"
)};`,
"// see webpack/lib/util/semver.js for original code",
"var n=/^([^-+]+)?(?:-([^+]+))?(?:\\+(.+))?$/.exec(str),p=n[1]?splitAndConvert(n[1]):[];return n[2]&&(p.length++,p.push.apply(p,splitAndConvert(n[2]))),n[3]&&(p.push([]),p.push.apply(p,splitAndConvert(n[3]))),p"
])}`;
/* eslint-disable eqeqeq */
/**
* @param {string} a version
@ -96,18 +83,14 @@ const versionLt = (a, b) => {
/* eslint-enable eqeqeq */
exports.versionLt = versionLt;
// must be a minimized version of above
exports.versionLtRuntimeCode = runtimeTemplate =>
`var versionLt = ${runtimeTemplate.basicFunction("a, b", [
"// see webpack/lib/util/semver.js for original code",
'a=parseVersion(a),b=parseVersion(b);for(var r=0;;){if(r>=a.length)return r<b.length&&"u"!=(typeof b[r])[0];var t=a[r],e=(typeof t)[0];if(r>=b.length)return"u"==e;var n=b[r],f=(typeof n)[0];if(e!=f)return"o"==e&&"n"==f||("s"==f||"u"==e);if("o"!=e&&"u"!=e&&t!=n)return t<n;r++}'
])}`;
/**
* @param {string} str range string
* @returns {SemVerRange} parsed range
*/
exports.parseRange = str => {
const splitAndConvert = str => {
return str.split(".").map(item => (`${+item}` === item ? +item : item));
};
// see https://docs.npmjs.com/misc/semver#range-grammar for grammar
const parsePartial = str => {
const match = /^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(str);
@ -288,13 +271,6 @@ const rangeToString = range => {
/* eslint-enable eqeqeq */
exports.rangeToString = rangeToString;
// must be a minimized version of above
exports.rangeToStringRuntimeCode = runtimeTemplate =>
`var rangeToString = ${runtimeTemplate.basicFunction("range", [
"// see webpack/lib/util/semver.js for original code",
'if(1===range.length)return"*";if(0 in range){var r="",n=range[0];r+=0==n?">=":-1==n?"<":1==n?"^":2==n?"~":n>0?"=":"!=";for(var e=1,a=1;a<range.length;a++){e--,r+="u"==(typeof(t=range[a]))[0]?"-":(e>0?".":"")+(e=2,t)}return r}var g=[];for(a=1;a<range.length;a++){var t=range[a];g.push(0===t?"not("+o()+")":1===t?"("+o()+" || "+o()+")":2===t?g.pop()+" "+g.pop():rangeToString(t))}return o();function o(){return g.pop().replace(/^\\((.+)\\)$/,"$1")}'
])}`;
/* eslint-disable eqeqeq */
/**
* @param {SemVerRange} range version range
@ -443,13 +419,6 @@ const satisfy = (range, version) => {
/* eslint-enable eqeqeq */
exports.satisfy = satisfy;
// must be a minimized version of above
exports.satisfyRuntimeCode = runtimeTemplate =>
`var satisfy = ${runtimeTemplate.basicFunction("range, version", [
"// see webpack/lib/util/semver.js for original code",
'if(0 in range){version=parseVersion(version);var e=range[0],r=e<0;r&&(e=-e-1);for(var n=0,i=1,f=!0;;i++,n++){var a,s,t=i<range.length?(typeof range[i])[0]:"";if(n>=version.length||"o"==(s=(typeof(a=version[n]))[0]))return!f||("u"==t?i>e&&!r:""==t!=r);if("u"==s){if(!f||"u"!=t)return!1}else if(f)if(t==s)if(i<=e){if(a!=range[i])return!1}else{if(r?a>range[i]:a<range[i])return!1;a!=range[i]&&(f=!1)}else if("s"!=t&&"n"!=t){if(r||i<=e)return!1;f=!1,i--}else{if(i<=e||s<t!=r)return!1;f=!1}else"s"!=t&&"n"!=t&&(f=!1,i--)}}var g=[],o=g.pop.bind(g);for(n=1;n<range.length;n++){var u=range[n];g.push(1==u?o()|o():2==u?o()&o():u?satisfy(u,version):!o())}return!!o()'
])}`;
exports.stringifyHoley = json => {
switch (typeof json) {
case "undefined":
@ -470,3 +439,39 @@ exports.stringifyHoley = json => {
return JSON.stringify(json);
}
};
//#region runtime code: parseVersion
exports.parseVersionRuntimeCode = runtimeTemplate =>
`var parseVersion = ${runtimeTemplate.basicFunction("str", [
"// see webpack/lib/util/semver.js for original code",
`var p=${
runtimeTemplate.supportsArrowFunction() ? "p=>" : "function(p)"
}{return p.split(".").map((${
runtimeTemplate.supportsArrowFunction() ? "p=>" : "function(p)"
}{return+p==p?+p:p}))},n=/^([^-+]+)?(?:-([^+]+))?(?:\\+(.+))?$/.exec(str),r=n[1]?p(n[1]):[];return n[2]&&(r.length++,r.push.apply(r,p(n[2]))),n[3]&&(r.push([]),r.push.apply(r,p(n[3]))),r;`
])}`;
//#endregion
//#region runtime code: versionLt
exports.versionLtRuntimeCode = runtimeTemplate =>
`var versionLt = ${runtimeTemplate.basicFunction("a, b", [
"// see webpack/lib/util/semver.js for original code",
'a=parseVersion(a),b=parseVersion(b);for(var r=0;;){if(r>=a.length)return r<b.length&&"u"!=(typeof b[r])[0];var e=a[r],n=(typeof e)[0];if(r>=b.length)return"u"==n;var t=b[r],f=(typeof t)[0];if(n!=f)return"o"==n&&"n"==f||("s"==f||"u"==n);if("o"!=n&&"u"!=n&&e!=t)return e<t;r++}'
])}`;
//#endregion
//#region runtime code: rangeToString
exports.rangeToStringRuntimeCode = runtimeTemplate =>
`var rangeToString = ${runtimeTemplate.basicFunction("range", [
"// see webpack/lib/util/semver.js for original code",
'if(1===range.length)return"*";if(0 in range){var r="",n=range[0];r+=0==n?">=":-1==n?"<":1==n?"^":2==n?"~":n>0?"=":"!=";for(var e=1,a=1;a<range.length;a++){e--,r+="u"==(typeof(t=range[a]))[0]?"-":(e>0?".":"")+(e=2,t)}return r}var g=[];for(a=1;a<range.length;a++){var t=range[a];g.push(0===t?"not("+o()+")":1===t?"("+o()+" || "+o()+")":2===t?g.pop()+" "+g.pop():rangeToString(t))}return o();function o(){return g.pop().replace(/^\\((.+)\\)$/,"$1")}'
])}`;
//#endregion
//#region runtime code: satisfy
exports.satisfyRuntimeCode = runtimeTemplate =>
`var satisfy = ${runtimeTemplate.basicFunction("range, version", [
"// see webpack/lib/util/semver.js for original code",
'if(0 in range){version=parseVersion(version);var e=range[0],r=e<0;r&&(e=-e-1);for(var n=0,i=1,a=!0;;i++,n++){var f,s,g=i<range.length?(typeof range[i])[0]:"";if(n>=version.length||"o"==(s=(typeof(f=version[n]))[0]))return!a||("u"==g?i>e&&!r:""==g!=r);if("u"==s){if(!a||"u"!=g)return!1}else if(a)if(g==s)if(i<=e){if(f!=range[i])return!1}else{if(r?f>range[i]:f<range[i])return!1;f!=range[i]&&(a=!1)}else if("s"!=g&&"n"!=g){if(r||i<=e)return!1;a=!1,i--}else{if(i<=e||s<g!=r)return!1;a=!1}else"s"!=g&&"n"!=g&&(a=!1,i--)}}var t=[],o=t.pop.bind(t);for(n=1;n<range.length;n++){var u=range[n];t.push(1==u?o()|o():2==u?o()&o():u?satisfy(u,version):!o())}return!!o();'
])}`;
//#endregion

View File

@ -86,6 +86,7 @@
"simple-git": "^1.65.0",
"strip-ansi": "^6.0.0",
"style-loader": "^1.0.0",
"terser": "^4.8.0",
"toml": "^3.0.0",
"tooling": "webpack/tooling#v1.8.0",
"ts-loader": "^6.0.4",
@ -144,8 +145,8 @@
"type-lint": "tsc",
"typings-lint": "tsc -p tsconfig.test.json",
"spellcheck": "cspell \"{.github,benchmark,bin,examples,hot,lib,schemas,setup,tooling}/**/*.{md,yml,yaml,js,json}\" \"*.md\"",
"special-lint": "node node_modules/tooling/lockfile-lint && node node_modules/tooling/schemas-lint && node node_modules/tooling/inherit-types && node node_modules/tooling/format-schemas && node node_modules/tooling/format-file-header && node node_modules/tooling/compile-to-definitions && node node_modules/tooling/generate-types",
"special-lint-fix": "node node_modules/tooling/inherit-types --write && node node_modules/tooling/format-schemas --write && node node_modules/tooling/format-file-header --write && node node_modules/tooling/compile-to-definitions --write && node node_modules/tooling/generate-types --write",
"special-lint": "node node_modules/tooling/lockfile-lint && node node_modules/tooling/schemas-lint && node node_modules/tooling/inherit-types && node node_modules/tooling/format-schemas && node tooling/generate-runtime-code.js && node node_modules/tooling/format-file-header && node node_modules/tooling/compile-to-definitions && node node_modules/tooling/generate-types",
"special-lint-fix": "node node_modules/tooling/inherit-types --write && node node_modules/tooling/format-schemas --write && node tooling/generate-runtime-code.js --write && node node_modules/tooling/format-file-header --write && node node_modules/tooling/compile-to-definitions --write && node node_modules/tooling/generate-types --write",
"fix": "yarn code-lint --fix && yarn special-lint-fix && yarn pretty-lint-fix",
"pretty-lint-base": "prettier \"*.{ts,json,yml,yaml,md}\" \"{setup,lib,bin,hot,benchmark,tooling,schemas}/**/*.json\" \"examples/*.md\"",
"pretty-lint-base-all": "yarn pretty-lint-base \"*.js\" \"{setup,lib,bin,hot,benchmark,tooling,schemas}/**/*.js\" \"test/*.js\" \"test/helpers/*.js\" \"test/{configCases,watchCases,statsCases,hotCases,benchmarkCases}/**/webpack.config.js\" \"examples/**/webpack.config.js\"",

View File

@ -1,186 +0,0 @@
const fs = require("fs");
const path = require("path");
const prettier = require("prettier");
const baseSchema = require("../schemas/WebpackOptions.json");
const schemasDir = path.resolve(__dirname, "../schemas");
const baseDefs = new Map(Object.entries(baseSchema.definitions));
// When --write is set, files will be written in place
// Otherwise it only prints outdated files
const doWrite = process.argv.includes("--write");
const sortObjectAlphabetically = obj => {
const keys = Object.keys(obj).sort();
const newObj = {};
for (const key of keys) {
newObj[key] = obj[key];
}
return newObj;
};
const typeOrder = [
"array",
"enum",
"RegExp",
"number",
"boolean",
"string",
"object",
"Function",
undefined
];
const sortArrayByType = array => {
array.sort((a, b) => {
const aType = a.type || a.instanceof || (a.enum && "enum");
const bType = b.type || b.instanceof || (b.enum && "enum");
const aPos = typeOrder.indexOf(aType);
const bPos = typeOrder.indexOf(bType);
if (aPos === bPos) {
return array.indexOf(a) - array.indexOf(b);
}
return aPos - bPos;
});
};
const sortObjectWithList = (obj, props) => {
const keys = Object.keys(obj)
.filter(p => !props.includes(p))
.sort();
const newObj = {};
for (const key of props) {
if (key in obj) {
newObj[key] = obj[key];
}
}
for (const key of keys) {
newObj[key] = obj[key];
}
return newObj;
};
const PROPERTIES = [
"$ref",
"definitions",
"$id",
"id",
"title",
"description",
"type",
"cli",
"items",
"minItems",
"uniqueItems",
"additionalProperties",
"properties",
"required",
"minProperties",
"oneOf",
"anyOf",
"allOf",
"enum",
"absolutePath",
"minLength",
"minimum",
"instanceof",
"tsType"
];
const NESTED_WITH_NAME = ["definitions", "properties"];
const NESTED_DIRECT = ["items", "additionalProperties"];
const NESTED_ARRAY = ["oneOf", "anyOf", "allOf"];
const processJson = json => {
json = sortObjectWithList(json, PROPERTIES);
if (json.definitions) {
json.definitions = { ...json.definitions };
for (const key of Object.keys(json.definitions)) {
const baseDef = baseDefs.get(key);
if (baseDef) {
json.definitions[key] = baseDef;
}
}
}
for (const name of NESTED_WITH_NAME) {
if (name in json && json[name] && typeof json[name] === "object") {
json[name] = sortObjectAlphabetically(json[name]);
for (const key in json[name]) {
json[name][key] = processJson(json[name][key]);
}
}
}
for (const name of NESTED_DIRECT) {
if (name in json && json[name] && typeof json[name] === "object") {
json[name] = processJson(json[name]);
}
}
for (const name of NESTED_ARRAY) {
if (name in json && Array.isArray(json[name])) {
for (let i = 0; i < json[name].length; i++) {
json[name][i] = processJson(json[name][i]);
}
sortArrayByType(json[name]);
}
}
return json;
};
const formatSchema = schemaPath => {
const json = require(schemaPath);
const processedJson = processJson(json);
const rawString = JSON.stringify(processedJson, null, 2);
prettier.resolveConfig(schemaPath).then(config => {
config.filepath = schemaPath;
config.parser = "json";
const prettyString = prettier.format(rawString, config);
let normalizedContent = "";
try {
const content = fs.readFileSync(schemaPath, "utf-8");
normalizedContent = content.replace(/\r\n?/g, "\n");
} catch (e) {
// ignore
}
if (normalizedContent.trim() !== prettyString.trim()) {
const basename = path.relative(schemasDir, schemaPath);
if (doWrite) {
fs.writeFileSync(schemaPath, prettyString, "utf-8");
console.error(`schemas/${basename.replace(/\\/g, "/")} updated`);
} else {
console.error(
`schemas/${basename.replace(/\\/g, "/")} need to be updated`
);
process.exitCode = 1;
}
}
});
};
// include the top level folder "./schemas" by default
const dirs = new Set([schemasDir]);
// search for all nestedDirs inside of this folder
for (let dirWithSchemas of dirs) {
for (let item of fs.readdirSync(dirWithSchemas)) {
const absPath = path.resolve(dirWithSchemas, item);
if (fs.statSync(absPath).isDirectory()) {
dirs.add(absPath);
} else if (item.endsWith(".json")) {
formatSchema(absPath);
}
}
}

View File

@ -0,0 +1,87 @@
const path = require("path");
const fs = require("fs");
const terser = require("terser");
const prettier = require("prettier");
// When --write is set, files will be written in place
// Otherwise it only prints outdated files
const doWrite = process.argv.includes("--write");
const files = ["lib/util/semver.js"];
(async () => {
for (const file of files) {
const filePath = path.resolve(__dirname, "..", file);
const content = fs.readFileSync(filePath, "utf-8");
const exports = require(`../${file}`);
const regexp = /\n\/\/#region runtime code: (.+)\n[\s\S]+?\/\/#endregion\n/g;
const replaces = new Map();
let match = regexp.exec(content);
while (match) {
const [fullMatch, name] = match;
const originalCode = exports[name].toString();
const header = /^\(?([^=)]+)\)?\s=> \{/.exec(originalCode);
const body = originalCode.slice(header[0].length, -1);
const result = await terser.minify(
{
["input.js"]: body
},
{
compress: true,
mangle: true,
ecma: 5,
toplevel: true,
parse: {
bare_returns: true
}
}
);
const args = header[1];
if (/`|const|let|=>|\.\.\./.test(result.code)) {
throw new Error(`Code Style of ${name} in ${file} is too high`);
}
let templateLiteral = false;
const code = result.code
.replace(/\\/g, "\\\\")
.replace(/'/g, "\\'")
.replace(/function\(([^)]+)\)/g, (m, args) => {
templateLiteral = true;
return `\${runtimeTemplate.supportsArrowFunction() ? '${
args.includes(",") ? `(${args})` : args
}=>' : 'function(${args})'}`;
});
replaces.set(
fullMatch,
`
//#region runtime code: ${name}
exports.${name}RuntimeCode = runtimeTemplate => \`var ${name} = \${runtimeTemplate.basicFunction("${args}", [
"// see webpack/${file} for original code",
${templateLiteral ? `\`${code}\`` : `'${code}'`}
])}\`;
//#endregion
`
);
match = regexp.exec(content);
}
const prettierConfig = prettier.resolveConfig.sync(filePath);
const newContent = prettier.format(
content.replace(regexp, match => replaces.get(match)),
{ filepath: filePath, ...prettierConfig }
);
if (newContent !== content) {
if (doWrite) {
fs.writeFileSync(filePath, newContent, "utf-8");
console.error(`${file} updated`);
} else {
console.error(`${file} need to be updated`);
process.exitCode = 1;
}
}
}
})();