159 lines
3.2 KiB
JavaScript
159 lines
3.2 KiB
JavaScript
'use strict';
|
|
const path = require('node:path');
|
|
const fs = require('node:fs');
|
|
const getDocumentationUrl = require('./get-documentation-url.js');
|
|
|
|
const isIterable = object => typeof object?.[Symbol.iterator] === 'function';
|
|
|
|
class FixAbortError extends Error {}
|
|
const fixOptions = {
|
|
abort() {
|
|
throw new FixAbortError('Fix aborted.');
|
|
},
|
|
};
|
|
|
|
function wrapFixFunction(fix) {
|
|
return fixer => {
|
|
const result = fix(fixer, fixOptions);
|
|
|
|
if (isIterable(result)) {
|
|
try {
|
|
return [...result];
|
|
} catch (error) {
|
|
if (error instanceof FixAbortError) {
|
|
return;
|
|
}
|
|
|
|
/* c8 ignore next */
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
};
|
|
}
|
|
|
|
function reportListenerProblems(listener, context) {
|
|
// Listener arguments can be `codePath, node` or `node`
|
|
return function (...listenerArguments) {
|
|
let problems = listener(...listenerArguments);
|
|
|
|
if (!problems) {
|
|
return;
|
|
}
|
|
|
|
if (!isIterable(problems)) {
|
|
problems = [problems];
|
|
}
|
|
|
|
for (const problem of problems) {
|
|
if (problem.fix) {
|
|
problem.fix = wrapFixFunction(problem.fix);
|
|
}
|
|
|
|
if (Array.isArray(problem.suggest)) {
|
|
for (const suggest of problem.suggest) {
|
|
if (suggest.fix) {
|
|
suggest.fix = wrapFixFunction(suggest.fix);
|
|
}
|
|
|
|
suggest.data = {
|
|
...problem.data,
|
|
...suggest.data,
|
|
};
|
|
}
|
|
}
|
|
|
|
context.report(problem);
|
|
}
|
|
};
|
|
}
|
|
|
|
// `checkVueTemplate` function will wrap `create` function, there is no need to wrap twice
|
|
const wrappedFunctions = new Set();
|
|
function reportProblems(create) {
|
|
if (wrappedFunctions.has(create)) {
|
|
return create;
|
|
}
|
|
|
|
const wrapped = context => {
|
|
const listeners = create(context);
|
|
|
|
if (!listeners) {
|
|
return {};
|
|
}
|
|
|
|
return Object.fromEntries(
|
|
Object.entries(listeners)
|
|
.map(([selector, listener]) => [selector, reportListenerProblems(listener, context)]),
|
|
);
|
|
};
|
|
|
|
wrappedFunctions.add(wrapped);
|
|
|
|
return wrapped;
|
|
}
|
|
|
|
function checkVueTemplate(create, options) {
|
|
const {
|
|
visitScriptBlock,
|
|
} = {
|
|
visitScriptBlock: true,
|
|
...options,
|
|
};
|
|
|
|
create = reportProblems(create);
|
|
|
|
const wrapped = context => {
|
|
const listeners = create(context);
|
|
|
|
// `vue-eslint-parser`
|
|
if (context.parserServices?.defineTemplateBodyVisitor) {
|
|
return visitScriptBlock
|
|
? context.parserServices.defineTemplateBodyVisitor(listeners, listeners)
|
|
: context.parserServices.defineTemplateBodyVisitor(listeners);
|
|
}
|
|
|
|
return listeners;
|
|
};
|
|
|
|
wrappedFunctions.add(wrapped);
|
|
return wrapped;
|
|
}
|
|
|
|
/** @returns {import('eslint').Rule.RuleModule} */
|
|
function loadRule(ruleId) {
|
|
const rule = require(`../${ruleId}`);
|
|
|
|
return {
|
|
meta: {
|
|
// If there is are, options add `[]` so ESLint can validate that no data is passed to the rule.
|
|
// https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/require-meta-schema.md
|
|
schema: [],
|
|
...rule.meta,
|
|
docs: {
|
|
...rule.meta.docs,
|
|
url: getDocumentationUrl(ruleId),
|
|
},
|
|
},
|
|
create: reportProblems(rule.create),
|
|
};
|
|
}
|
|
|
|
function loadRules() {
|
|
return Object.fromEntries(
|
|
fs.readdirSync(path.join(__dirname, '..'), {withFileTypes: true})
|
|
.filter(file => file.isFile())
|
|
.map(file => {
|
|
const ruleId = path.basename(file.name, '.js');
|
|
return [ruleId, loadRule(ruleId)];
|
|
}),
|
|
);
|
|
}
|
|
|
|
module.exports = {
|
|
loadRule,
|
|
loadRules,
|
|
checkVueTemplate,
|
|
};
|