Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const path = require('path');
const js = require('@eslint/js');
const globals = require('globals');
const vue = require('eslint-plugin-vue');
Expand Down Expand Up @@ -50,6 +51,72 @@ const browserFiles = [
'vitest.config.mjs',
];

// Keep these aliases in sync with vite.config.mjs. ESLint's CommonJS config cannot import it.
const aliases = { _: __dirname, '@': path.join(__dirname, 'src') };

const resolveSpecifier = (fromDir, spec) => {
const alias = Object.keys(aliases).find((prefix) => spec === prefix || spec.startsWith(`${prefix}/`));
if (alias) {
return path.resolve(aliases[alias], spec.slice(alias.length + (spec === alias ? 0 : 1)));
}
return spec.startsWith('.') ? path.resolve(fromDir, spec) : null;
};

const resolvedJsonCache = new Map();
const resolvesToJson = (target) => {
if (!resolvedJsonCache.has(target)) {
try {
resolvedJsonCache.set(target, require.resolve(target).endsWith('.json'));
} catch {
resolvedJsonCache.set(target, false);
}
}
return resolvedJsonCache.get(target);
};

const jsonExtensions = {
rules: {
'explicit-json-extension': {
meta: {
type: 'problem',
fixable: 'code',
schema: [],
messages: {
missing: `'{{spec}}' resolves to a JSON file; write '{{spec}}.json' so the editor can resolve it (see #1397).`,
},
},
create(context) {
const fromDir = path.dirname(context.filename);
const check = (node) => {
if (node?.type !== 'Literal' || typeof node.value !== 'string' || node.value.endsWith('.json')) {
return;
}
const target = resolveSpecifier(fromDir, node.value);
if (!target || !resolvesToJson(target)) {
return;
}
context.report({
node,
messageId: 'missing',
data: { spec: node.value },
fix: (fixer) => {
const raw = context.sourceCode.getText(node);
return fixer.replaceText(node, `${raw.slice(0, -1)}.json${raw.slice(-1)}`);
},
});
};
return {
'CallExpression[callee.name="require"]': (node) => check(node.arguments[0]),
ImportDeclaration: (node) => check(node.source),
ImportExpression: (node) => check(node.source),
ExportNamedDeclaration: (node) => check(node.source),
ExportAllDeclaration: (node) => check(node.source),
};
},
},
},
};

module.exports = [
{
// The worktree dirs are git-ignored, but that doesn't stop eslint walking into them: each
Expand All @@ -66,13 +133,15 @@ module.exports = [
vuetify,
prettier,
'no-only-tests': noOnlyTests,
'json-extensions': jsonExtensions,
},
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
globals: nodeGlobals,
},
rules: {
'json-extensions/explicit-json-extension': 'error',
'eol-last': [ 'error', 'always' ],
'max-len': [
'warn',
Expand Down
Loading