webpack/tooling/format-file-header.js

184 lines
4.6 KiB
JavaScript

const path = require("path");
const fs = require("fs");
// When --write is set, files will be written in place
// Elsewise it only prints outdated files
const doWrite = process.argv.includes("--write");
const allFiles = new Set();
const findFiles = p => {
const s = fs.statSync(p);
if (s.isDirectory()) {
for (const name of fs.readdirSync(p)) {
if (name.startsWith(".")) continue;
findFiles(path.join(p, name));
}
} else if (s.isFile()) {
allFiles.add(p);
}
};
findFiles(path.resolve(__dirname, "../lib"));
let canUpdateWithWrite = false;
const sortImport = (a, b) => {
if (!a.key.startsWith(".") && b.key.startsWith(".")) return -1;
if (a.key.startsWith(".") && !b.key.startsWith(".")) return 1;
if (a.key < b.key) return -1;
if (a.key > b.key) return 1;
return 0;
};
const execToArray = (content, regexp) => {
const items = [];
let match = regexp.exec(content);
while (match) {
items.push({
content: match[0],
key: match[1] + match[2]
});
match = regexp.exec(content);
}
return items;
};
const schema = [
{
title: "license comment",
regexp: /\/\*\n\s*MIT License http:\/\/www\.opensource\.org\/licenses\/mit-license\.php\n\s*(?:(Authors? .+)\n)?\s*\*\/\n/g,
updateMessage: "update the license comment",
update(content, author) {
return (
[
"/*",
"\tMIT License http://www.opensource.org/licenses/mit-license.php",
author && `\t${author}`,
"*/"
]
.filter(Boolean)
.join("\n") + "\n"
);
}
},
{
title: "new line after license comment",
regexp: /\n?/g,
updateMessage: "insert a new line after the license comment",
update() {
return "\n";
}
},
{
title: "strict mode",
regexp: /"use strict";\n/g
},
{
title: "new line after strict mode",
regexp: /\n?/g,
updateMessage: 'insert a new line after "use strict"',
update() {
return "\n";
}
},
{
title: "imports",
regexp: /(const (\{\s+\w+(,\s+\w+)*\s+\}|\w+) = (\/\*\* @type \{TODO\} \*\/\s\()?require\("[^"]+"\)\)?(\.\w+)*;\n)+\n/g,
updateMessage: "sort imports alphabetically",
update(content) {
const items = execToArray(
content,
/const (?:\{\s+\w+(?:,\s+\w+)*\s+\}|\w+) = (?:\/\*\* @type \{TODO\} \*\/\s\()?require\("([^"]+)"\)\)?((?:\.\w+)*);\n/g
);
items.sort(sortImport);
return items.map(item => item.content).join("") + "\n";
},
optional: true,
repeat: true
},
{
title: "type imports",
regexp: /(\/\*\* (?:@template \w+ )*@typedef \{import\("[^"]+"\)(\.\w+)*(?:<(?:(?:\w\.)*\w+, )*(?:\w\.)*\w+>)?\} \w+(?:<(?:(?:\w\.)*\w+, )*(?:\w\.)*\w+>)? \*\/\n)+\n/g,
updateMessage: "sort type imports alphabetically",
update(content) {
const items = execToArray(
content,
/\/\*\* (?:@template \w+ )*@typedef \{import\("([^"]+)"\)((?:\.\w+)*(?:<(?:(?:\w\.)*\w+, )*(?:\w\.)*\w+>)?)\} \w+(?:<(?:(?:\w\.)*\w+, )*(?:\w\.)*\w+>)? \*\/\n/g
);
items.sort(sortImport);
return items.map(item => item.content).join("") + "\n";
},
optional: true,
repeat: true
}
];
for (const filePath of allFiles) {
let content = fs.readFileSync(filePath, "utf-8");
const nl = /(\r?\n)/.exec(content);
content = content.replace(/\r?\n/g, "\n");
let newContent = content;
let state = 0;
let pos = 0;
// eslint-disable-next-line no-constant-condition
while (true) {
const current = schema[state];
if (!current) break;
current.regexp.lastIndex = pos;
const match = current.regexp.exec(newContent);
if (!match) {
if (current.optional) {
state++;
continue;
}
console.log(`${filePath}: Missing ${current.title} at ${pos}`);
process.exitCode = 1;
break;
}
if (match.index !== pos) {
console.log(
`${filePath}: Unexpected code at ${pos}-${match.index}, expected ${current.title}`
);
process.exitCode = 1;
pos = match.index;
}
if (!current.repeat) {
state++;
}
if (current.update) {
const update = current.update(...match);
if (update !== match[0]) {
newContent =
newContent.substr(0, pos) +
update +
newContent.slice(pos + match[0].length);
pos += update.length;
if (!doWrite) {
const updateMessage =
current.updateMessage || `${current.title} need to be updated`;
console.log(`${filePath}: ${updateMessage}`);
}
continue;
}
}
pos += match[0].length;
}
if (newContent !== content) {
if (doWrite) {
if (nl) {
newContent = newContent.replace(/\n/g, nl[0]);
}
fs.writeFileSync(filePath, newContent, "utf-8");
console.log(filePath);
} else {
canUpdateWithWrite = true;
process.exitCode = 1;
}
}
}
if (canUpdateWithWrite) {
console.log("Run 'yarn fix' to try to fix the problem automatically");
}