@putout/operator-match-files
Version:
🐊Putout operator adds ability to match files to plugins
216 lines (162 loc) • 5.41 kB
JavaScript
;
const path = require('node:path');
const {parse, print} = require('@putout/engine-parser');
const {transform} = require('putout/transform');
const {findPlaces} = require('putout/find-places');
const ignores = require('putout/ignores');
const {toJS, fromJS} = require('@putout/operator-json');
const {
readFileContent,
findFile,
writeFileContent,
getFilename,
createFile,
removeFile,
getParentDirectory,
} = require('@putout/operator-filesystem');
const {join} = path;
const isObject = (a) => a && typeof a === 'object';
const {entries} = Object;
const report = (path, {message}) => message;
module.exports.matchFiles = (options) => {
const {filename} = options;
const files = options.files ?? options;
const exclude = options.exclude ?? [];
check(files);
const scan = createScan({
defaultFilename: filename,
files,
exclude,
});
return {
fix,
scan,
report,
};
};
function fix(inputFile, {dirPath, matchInputFilename, outputFilename, matchedJS, matchedAST, options}) {
transform(matchedAST, matchedJS, options);
const matchedJSON = magicPrint(outputFilename, matchedAST);
const outputFile = getOutputFile({
dirPath,
matchInputFilename,
outputFilename,
inputFile,
});
writeFileContent(outputFile, matchedJSON);
if (inputFile !== outputFile)
removeFile(inputFile);
}
const createScan = ({files, exclude, defaultFilename}) => (mainPath, {push, progress, options}) => {
const allFiles = [];
const cwd = getFilename(mainPath);
options.filename = options.filename ?? defaultFilename;
for (const [filename, rawOptions] of entries(files)) {
const [matchInputFilenameMask] = parseMatcher(filename, options);
const inputFiles = findFile(mainPath, matchInputFilenameMask, exclude);
for (const inputFile of inputFiles) {
const dirPath = getParentDirectory(inputFile);
const inputFilename = getFilename(inputFile);
const [matchInputFilename, outputFilename = matchInputFilename] = parseMatcher(filename, {
filename: inputFilename,
});
if (ignores(cwd, inputFilename, options))
continue;
allFiles.push({
dirPath,
matchInputFilename,
rawOptions,
inputFile,
inputFilename,
outputFilename,
});
}
}
const n = allFiles.length;
for (const [i, current] of allFiles.entries()) {
const {
dirPath,
matchInputFilename,
inputFile,
inputFilename,
outputFilename,
rawOptions,
} = current;
progress({
i,
n,
});
const fileContent = readFileContent(inputFile) || '{}';
const [matchedJS, matchedAST] = magicParse(inputFilename, fileContent);
const options = parseOptions(inputFilename, rawOptions);
const places = findPlaces(matchedAST, matchedJS, options);
if (!places.length)
continue;
const {message} = places[0];
push(inputFile, {
dirPath,
matchInputFilename,
outputFilename,
message,
options,
matchedAST,
matchedJS,
});
}
};
function magicParse(name, content) {
if (/\.json$/.test(name)) {
const js = toJS(content);
const ast = parse(js);
return [js, ast];
}
if (/\.(c|m)?ts(x)?$/.test(name)) {
const ast = parse(content, {
isTS: true,
});
return [content, ast];
}
return [content, parse(content)];
}
function magicPrint(name, ast) {
if (/\.json$/.test(name)) {
const js = print(ast);
return fromJS(js);
}
return print(ast);
}
function check(files) {
for (const [, plugin] of entries(files)) {
if (!isObject(plugin))
throw Error(`☝️ Looks like provided to 'matchFiles()' typeof of plugin is not an 'object' but '${typeof plugin}'`);
}
}
function getOutputFile({dirPath, matchInputFilename, outputFilename, inputFile}) {
if (matchInputFilename === outputFilename)
return inputFile;
const name = join(getFilename(dirPath), outputFilename);
const [outputFile] = findFile(dirPath, name);
if (outputFile)
return outputFile;
return createFile(dirPath, outputFilename);
}
function parseMatcher(matcher, options) {
const {filename} = options;
if (!filename)
return matcher.split(' -> ');
const {ext, name} = path.parse(filename);
matcher = matcher.replaceAll(`__name`, name);
matcher = matcher.replaceAll(`__ext`, ext);
return matcher.split(' -> ');
}
function parseOptions(inputFilename, rawOptions) {
if (rawOptions.plugins)
return rawOptions;
const name = `match-file: ${inputFilename}`;
const plugins = [
[name, rawOptions],
];
return {
plugins,
};
}