286 lines
8.6 KiB
JavaScript
286 lines
8.6 KiB
JavaScript
import { composer } from 'eslint-flat-config-utils';
|
|
import gitignore from 'eslint-config-flat-gitignore';
|
|
import { join } from 'pathe';
|
|
import process from 'node:process';
|
|
import { isPackageExists } from 'local-pkg';
|
|
import nuxtPlugin from '@nuxt/eslint-plugin';
|
|
import pluginESLint from '@eslint/js';
|
|
import globals from 'globals';
|
|
|
|
const GLOB_EXTS = "{js,ts,jsx,tsx,vue}";
|
|
|
|
const parserPlain = {
|
|
meta: {
|
|
name: "parser-plain"
|
|
},
|
|
parseForESLint: (code) => ({
|
|
ast: {
|
|
body: [],
|
|
comments: [],
|
|
loc: { end: code.length, start: 0 },
|
|
range: [0, code.length],
|
|
tokens: [],
|
|
type: "Program"
|
|
},
|
|
scopeManager: null,
|
|
services: { isPlain: true },
|
|
visitorKeys: {
|
|
Program: []
|
|
}
|
|
})
|
|
};
|
|
async function ensurePackages(packages) {
|
|
if (process.env.CI || !process.stdout.isTTY)
|
|
return;
|
|
const nonExistingPackages = packages.filter((i) => i && !isPackageExists(i));
|
|
if (nonExistingPackages.length === 0)
|
|
return;
|
|
const p = await import('@clack/prompts');
|
|
const result = await p.confirm({
|
|
message: `${nonExistingPackages.length === 1 ? "Package is" : "Packages are"} required for this config: ${nonExistingPackages.join(", ")}. Do you want to install them?`
|
|
});
|
|
if (result)
|
|
await import('@antfu/install-pkg').then((i) => i.installPackage(nonExistingPackages, { dev: true }));
|
|
}
|
|
async function interopDefault(m) {
|
|
const resolved = await m;
|
|
return resolved.default || resolved;
|
|
}
|
|
function removeUndefined(obj) {
|
|
return Object.fromEntries(Object.entries(obj).filter(([, value]) => value !== void 0));
|
|
}
|
|
function resolveOptions(config) {
|
|
if ("__resolved" in config) {
|
|
return config;
|
|
}
|
|
const dirs = {
|
|
...config.dirs
|
|
};
|
|
dirs.root ||= [".", "./app"];
|
|
dirs.src ||= dirs.root;
|
|
dirs.pages ||= dirs.src.map((src) => `${src}/pages`);
|
|
dirs.layouts ||= dirs.src.map((src) => `${src}/layouts`);
|
|
dirs.components ||= dirs.src.map((src) => `${src}/components`);
|
|
dirs.composables ||= dirs.src.map((src) => `${src}/composables`);
|
|
dirs.plugins ||= dirs.src.map((src) => `${src}/plugins`);
|
|
dirs.modules ||= dirs.src.map((src) => `${src}/modules`);
|
|
dirs.middleware ||= dirs.src.map((src) => `${src}/middleware`);
|
|
dirs.servers ||= dirs.src.map((src) => `${src}/servers`);
|
|
dirs.componentsPrefixed ||= [];
|
|
const resolved = {
|
|
features: {
|
|
standalone: true,
|
|
stylistic: false,
|
|
typescript: isPackageExists("typescript"),
|
|
tooling: false,
|
|
formatters: false,
|
|
nuxt: {},
|
|
import: {},
|
|
...config.features
|
|
},
|
|
dirs
|
|
};
|
|
Object.defineProperty(resolved, "__resolved", { value: true, enumerable: false });
|
|
return resolved;
|
|
}
|
|
|
|
function disables(options) {
|
|
const resolved = resolveOptions(options);
|
|
const dirs = resolved.dirs;
|
|
const nestedGlobPattern = `**/*.${GLOB_EXTS}`;
|
|
const fileRoutes = [.../* @__PURE__ */ new Set([
|
|
// These files must have one-word names as they have a special meaning in Nuxt.
|
|
...dirs.src.flatMap((layersDir) => [
|
|
join(layersDir, `app.${GLOB_EXTS}`),
|
|
join(layersDir, `error.${GLOB_EXTS}`)
|
|
]) || [],
|
|
// Layouts and pages are not used directly by users so they can have one-word names.
|
|
...dirs.layouts.map((layoutsDir) => join(layoutsDir, nestedGlobPattern)) || [],
|
|
...dirs.pages.map((pagesDir) => join(pagesDir, nestedGlobPattern)) || [],
|
|
// These files should have multiple words in their names as they are within subdirectories.
|
|
...dirs.components.map((componentsDir) => join(componentsDir, "*", nestedGlobPattern)) || [],
|
|
// Prefixed components can have one-word names in file
|
|
...dirs.componentsPrefixed.map((componentsDir) => join(componentsDir, nestedGlobPattern)) || []
|
|
])].sort();
|
|
const configs = [];
|
|
if (fileRoutes.length) {
|
|
configs.push({
|
|
name: "nuxt/disables/routes",
|
|
files: fileRoutes,
|
|
rules: {
|
|
"vue/multi-word-component-names": "off"
|
|
}
|
|
});
|
|
}
|
|
return configs;
|
|
}
|
|
|
|
function nuxt(options) {
|
|
const resolved = resolveOptions(options);
|
|
const dirs = resolved.dirs;
|
|
const fileSingleRoot = [
|
|
...dirs.layouts?.map((layoutsDir) => join(layoutsDir, `**/*.${GLOB_EXTS}`)) || [],
|
|
...dirs.pages?.map((pagesDir) => join(pagesDir, `**/*.${GLOB_EXTS}`)) || [],
|
|
...dirs.components?.map((componentsDir) => join(componentsDir, `**/*.server.${GLOB_EXTS}`)) || []
|
|
].sort();
|
|
const {
|
|
sortConfigKeys = !!options.features?.stylistic
|
|
} = options.features?.nuxt || {};
|
|
const configs = [];
|
|
configs.push({
|
|
name: "nuxt/setup",
|
|
plugins: {
|
|
nuxt: nuxtPlugin
|
|
},
|
|
languageOptions: {
|
|
globals: {
|
|
// Nuxt's runtime globals
|
|
$fetch: "readonly"
|
|
}
|
|
}
|
|
});
|
|
if (fileSingleRoot.length)
|
|
configs.push({
|
|
name: "nuxt/vue/single-root",
|
|
files: fileSingleRoot,
|
|
rules: {
|
|
"vue/no-multiple-template-root": "error"
|
|
}
|
|
});
|
|
configs.push({
|
|
name: "nuxt/rules",
|
|
rules: {
|
|
"nuxt/prefer-import-meta": "error"
|
|
}
|
|
});
|
|
configs.push({
|
|
name: "nuxt/nuxt-config",
|
|
files: [
|
|
"**/.config/nuxt.?([cm])[jt]s?(x)",
|
|
"**/nuxt.config.?([cm])[jt]s?(x)"
|
|
],
|
|
rules: {
|
|
"nuxt/no-nuxt-config-test-key": "error"
|
|
}
|
|
});
|
|
if (sortConfigKeys) {
|
|
configs.push({
|
|
name: "nuxt/sort-config",
|
|
files: [
|
|
"**/.config/nuxt.?([cm])[jt]s?(x)",
|
|
"**/nuxt.config.?([cm])[jt]s?(x)"
|
|
],
|
|
rules: {
|
|
"nuxt/nuxt-config-keys-order": "error"
|
|
}
|
|
});
|
|
}
|
|
return configs;
|
|
}
|
|
|
|
function ignores() {
|
|
return [
|
|
{
|
|
ignores: [
|
|
"**/dist",
|
|
"**/node_modules",
|
|
"**/.nuxt",
|
|
"**/.output",
|
|
"**/.vercel",
|
|
"**/.netlify",
|
|
"**/public"
|
|
]
|
|
}
|
|
];
|
|
}
|
|
|
|
function javascript() {
|
|
return [
|
|
{
|
|
...pluginESLint.configs.recommended,
|
|
name: "nuxt/javascript",
|
|
languageOptions: {
|
|
ecmaVersion: 2022,
|
|
parserOptions: {
|
|
ecmaFeatures: {
|
|
jsx: true
|
|
},
|
|
ecmaVersion: 2022,
|
|
sourceType: "module"
|
|
},
|
|
sourceType: "module",
|
|
globals: {
|
|
...globals.browser,
|
|
...globals.es2021,
|
|
...globals.node,
|
|
document: "readonly",
|
|
navigator: "readonly",
|
|
window: "readonly",
|
|
// This is technically not a global function, but it's a common practice in nuxt.config.ts,
|
|
// we include it here to avoid false positives.
|
|
defineNuxtConfig: "readonly"
|
|
}
|
|
},
|
|
linterOptions: {
|
|
reportUnusedDisableDirectives: true
|
|
}
|
|
}
|
|
];
|
|
}
|
|
|
|
function defineFlatConfigs(...configs) {
|
|
return composer(...configs);
|
|
}
|
|
function createConfigForNuxt(options = {}, ...userConfigs) {
|
|
const c = composer();
|
|
const resolved = resolveOptions(options);
|
|
if (resolved.features.standalone !== false) {
|
|
c.append(
|
|
gitignore({ strict: false }),
|
|
ignores(),
|
|
javascript(),
|
|
// Make these imports async, as they are optional and imports plugins
|
|
resolved.features.typescript !== false ? import('../chunks/typescript.mjs').then((m) => m.default(resolved)) : void 0,
|
|
import('../chunks/vue.mjs').then((m) => m.default(resolved)),
|
|
import('../chunks/import.mjs').then((m) => m.default(resolved))
|
|
);
|
|
}
|
|
c.append(
|
|
nuxt(resolved)
|
|
);
|
|
if (resolved.features.tooling) {
|
|
const toolingOptions = typeof resolved.features.tooling === "boolean" ? {} : resolved.features.tooling;
|
|
c.append(
|
|
toolingOptions.jsdoc !== false && import('../chunks/jsdoc.mjs').then((m) => m.default(resolved)),
|
|
toolingOptions.unicorn !== false && import('../chunks/unicorn.mjs').then((m) => m.default()),
|
|
toolingOptions.regexp !== false && import('../chunks/regexp.mjs').then((m) => m.default())
|
|
);
|
|
}
|
|
const stylisticOptions = typeof resolved.features.stylistic === "boolean" ? {} : resolved.features.stylistic;
|
|
if (resolved.features.stylistic) {
|
|
c.append(
|
|
import('../chunks/stylistic.mjs').then((m) => m.default(stylisticOptions))
|
|
);
|
|
}
|
|
if (resolved.features.formatters) {
|
|
c.append(
|
|
import('../chunks/formatters.mjs').then((m) => m.formatters(resolved.features.formatters, stylisticOptions))
|
|
);
|
|
}
|
|
c.append(
|
|
disables(resolved)
|
|
);
|
|
if (userConfigs.length > 0) {
|
|
c.append(...userConfigs);
|
|
}
|
|
c.setPluginConflictsError().setPluginConflictsError("import", [
|
|
'Different instances of plugin "{{pluginName}}" found in multiple configs:',
|
|
"{{configNames}}.",
|
|
"You might forget to set `standalone: false`.",
|
|
"Please refer to https://eslint.nuxt.com/packages/module#custom-config-presets.",
|
|
""
|
|
].join("\n"));
|
|
return c;
|
|
}
|
|
|
|
export { removeUndefined as a, createConfigForNuxt as c, defineFlatConfigs as d, ensurePackages as e, interopDefault as i, parserPlain as p, resolveOptions as r };
|