@storm-software/eslint
Version:
⚡ A package containing the base ESLint configuration used by Storm Software across many projects.
1,710 lines (1,696 loc) • 141 kB
JavaScript
import {
ensurePackages,
interopDefault,
isInEditorEnv,
isPackageInScope,
parserPlain,
renameRules
} from "./chunk-SZFP4QOA.js";
import {
getTsConfigPath
} from "./chunk-D3EN5HD2.js";
import {
banner_plugin_default
} from "./chunk-TBQNKXDY.js";
import "./chunk-XNCBYAYL.js";
import {
GLOB_ASTRO,
GLOB_ASTRO_TS,
GLOB_CSS,
GLOB_EXCLUDE,
GLOB_GRAPHQL,
GLOB_HTML,
GLOB_JSON,
GLOB_JSON5,
GLOB_JSONC,
GLOB_JSX,
GLOB_LESS,
GLOB_MARKDOWN,
GLOB_MARKDOWN_IN_MARKDOWN,
GLOB_MDX,
GLOB_POSTCSS,
GLOB_SCSS,
GLOB_SRC,
GLOB_SRC_EXT,
GLOB_SVG,
GLOB_TESTS,
GLOB_TOML,
GLOB_TS,
GLOB_TSX,
GLOB_XML,
GLOB_YAML
} from "./chunk-SRHSKJB5.js";
import {
findWorkspaceRoot
} from "./chunk-7JRBTALJ.js";
import {
joinPaths
} from "./chunk-G7QVU75O.js";
import {
__name
} from "./chunk-SHUYVCID.js";
// src/preset.ts
import { FlatConfigComposer } from "eslint-flat-config-utils";
import { isPackageExists as isPackageExists2 } from "local-pkg";
// src/configs/astro.ts
async function astro(options = {}) {
const { files = [
GLOB_ASTRO
], overrides = {}, stylistic: stylistic2 = true } = options;
const [pluginAstro, parserAstro, parserTs] = await Promise.all([
interopDefault(import("eslint-plugin-astro")),
interopDefault(import("astro-eslint-parser")),
interopDefault(import("@typescript-eslint/parser"))
]);
return [
{
name: "storm/astro/setup",
plugins: {
astro: pluginAstro
}
},
{
files,
languageOptions: {
globals: pluginAstro.environments.astro.globals,
parser: parserAstro,
parserOptions: {
extraFileExtensions: [
".astro"
],
parser: parserTs
},
sourceType: "module"
},
name: "storm/astro/rules",
processor: "astro/client-side-ts",
rules: {
// use recommended rules
"astro/missing-client-only-directive-value": "error",
"astro/no-conflict-set-directives": "error",
"astro/no-deprecated-astro-canonicalurl": "error",
"astro/no-deprecated-astro-fetchcontent": "error",
"astro/no-deprecated-astro-resolve": "error",
"astro/no-deprecated-getentrybyslug": "error",
"astro/no-set-html-directive": "off",
"astro/no-unused-define-vars-in-style": "error",
"astro/semi": "off",
"astro/valid-compile": "error",
...stylistic2 ? {
"style/indent": "off",
"style/jsx-closing-tag-location": "off",
"style/jsx-one-expression-per-line": "off",
"style/no-multiple-empty-lines": "off"
} : {},
...overrides
}
}
];
}
__name(astro, "astro");
// src/configs/cspell.ts
import cspellConfig from "@cspell/eslint-plugin/recommended";
async function cspell(options = {}) {
const { configFile = "./.vscode/cspell.json", overrides = {} } = options;
return [
{
name: "storm/cspell/rules",
...cspellConfig,
rules: {
...cspellConfig.rules,
"@cspell/spellchecker": [
"warn",
{
configFile: joinPaths(findWorkspaceRoot(), configFile),
generateSuggestions: true,
numSuggestions: 10,
autoFix: true
}
],
...overrides
}
}
];
}
__name(cspell, "cspell");
// src/configs/disables.ts
async function disables() {
return [
{
files: [
`**/scripts/${GLOB_SRC}`
],
name: "storm/disables/scripts",
rules: {
"no-console": "off",
"ts/explicit-function-return-type": "off"
}
},
{
files: [
`**/cli/${GLOB_SRC}`,
`**/cli.${GLOB_SRC_EXT}`
],
name: "storm/disables/cli",
rules: {
"no-console": "off"
}
},
{
files: [
"**/*.d.?([cm])ts"
],
name: "storm/disables/dts",
rules: {
"eslint-comments/no-unlimited-disable": "off",
"import/no-duplicates": "off",
"no-restricted-syntax": "off",
"unused-imports/no-unused-vars": "off"
}
},
{
files: [
"**/*.js",
"**/*.cjs"
],
name: "storm/disables/cjs",
rules: {
"ts/no-require-imports": "off"
}
},
{
files: [
`**/*.config.${GLOB_SRC_EXT}`,
`**/*.config.*.${GLOB_SRC_EXT}`
],
name: "storm/disables/config-files",
rules: {
"no-console": "off",
"ts/explicit-function-return-type": "off"
}
}
];
}
__name(disables, "disables");
// src/configs/formatters.ts
import defu from "defu";
// src/configs/stylistic.ts
var StylisticConfigDefaults = {
indent: 2,
jsx: true,
quotes: "double",
semi: true
};
async function stylistic(options = {}) {
const { indent = 2, jsx: jsx2 = true, overrides = {}, quotes = "double", semi = true, lineEndings = "unix" } = {
...StylisticConfigDefaults,
...options
};
const pluginStylistic = await interopDefault(import("@stylistic/eslint-plugin"));
const config2 = pluginStylistic.configs.customize({
indent,
jsx: jsx2,
pluginName: "style",
quotes,
semi
});
return [
{
name: "storm/stylistic/rules",
plugins: {
style: pluginStylistic
},
rules: {
...config2.rules,
"style/lines-around-comment": "off",
"style/linebreak-style": [
"error",
lineEndings
],
"style/comma-dangle": [
"error",
"never"
],
"style/comma-style": [
"error",
"last"
],
"style/quotes": [
"error",
quotes
],
"style/semi": [
"error",
semi ? "always" : "never"
],
"style/indent": [
"error",
indent,
{
SwitchCase: 1
}
],
"style/operator-linebreak": [
"error",
"after",
{
overrides: {
"=": "none",
"?": "before",
":": "before"
}
}
],
"style/jsx-indent": [
"error",
indent
],
"style/jsx-quotes": [
"error",
quotes === "single" ? "prefer-single" : "prefer-double"
],
"style/brace-style": [
"error",
"1tbs",
{
allowSingleLine: false
}
],
...overrides
}
}
];
}
__name(stylistic, "stylistic");
// src/configs/formatters.ts
function mergePrettierOptions(options, overrides = {}) {
return {
...options,
...overrides,
plugins: [
...overrides.plugins || [],
...options.plugins || []
]
};
}
__name(mergePrettierOptions, "mergePrettierOptions");
async function formatters(options = {}, stylistic2 = {}) {
if (options === true) {
const isPrettierPluginXmlInScope = isPackageInScope("@prettier/plugin-xml");
options = {
astro: isPackageInScope("prettier-plugin-astro"),
css: true,
graphql: true,
html: true,
markdown: true,
svg: isPrettierPluginXmlInScope,
xml: isPrettierPluginXmlInScope
};
}
await ensurePackages([
"eslint-plugin-format",
options.astro ? "prettier-plugin-astro" : void 0,
options.xml || options.svg ? "@prettier/plugin-xml" : void 0
]);
const { indent = 2, quotes = "double", semi = true } = {
...StylisticConfigDefaults,
...stylistic2
};
const prettierOptions = defu({
proseWrap: "always",
quoteProps: "preserve",
bracketSameLine: true,
bracketSpacing: true,
arrowParens: "avoid",
endOfLine: "lf",
printWidth: 120,
semi,
singleQuote: quotes === "single",
tabWidth: typeof indent === "number" ? indent : 2,
trailingComma: "none",
useTabs: indent === "tab"
}, options.prettierOptions ?? {});
const prettierXmlOptions = {
xmlQuoteAttributes: "double",
xmlSelfClosingSpace: true,
xmlSortAttributesByKey: false,
xmlWhitespaceSensitivity: "ignore"
};
const dprintOptions = defu({
indentWidth: typeof indent === "number" ? indent : 2,
quoteStyle: quotes === "single" ? "preferSingle" : "preferDouble",
useTabs: indent === "tab"
}, options.dprintOptions ?? {});
const pluginFormat = await interopDefault(import("eslint-plugin-format"));
const configs3 = [
{
name: "storm/formatter/setup",
plugins: {
format: pluginFormat
}
}
];
if (options.css) {
configs3.push({
files: [
GLOB_CSS,
GLOB_POSTCSS
],
languageOptions: {
parser: parserPlain
},
name: "storm/formatter/css",
rules: {
"format/prettier": [
"error",
mergePrettierOptions(prettierOptions, {
parser: "css"
})
]
}
}, {
files: [
GLOB_SCSS
],
languageOptions: {
parser: parserPlain
},
name: "storm/formatter/scss",
rules: {
"format/prettier": [
"error",
mergePrettierOptions(prettierOptions, {
parser: "scss"
})
]
}
}, {
files: [
GLOB_LESS
],
languageOptions: {
parser: parserPlain
},
name: "storm/formatter/less",
rules: {
"format/prettier": [
"error",
mergePrettierOptions(prettierOptions, {
parser: "less"
})
]
}
});
}
if (options.html) {
configs3.push({
files: [
GLOB_HTML
],
languageOptions: {
parser: parserPlain
},
name: "storm/formatter/html",
rules: {
"format/prettier": [
"error",
mergePrettierOptions(prettierOptions, {
parser: "html"
})
]
}
});
}
if (options.xml) {
configs3.push({
files: [
GLOB_XML
],
languageOptions: {
parser: parserPlain
},
name: "storm/formatter/xml",
rules: {
"format/prettier": [
"error",
mergePrettierOptions({
...prettierXmlOptions,
...prettierOptions
}, {
parser: "xml",
plugins: [
"@prettier/plugin-xml"
]
})
]
}
});
}
if (options.svg) {
configs3.push({
files: [
GLOB_SVG
],
languageOptions: {
parser: parserPlain
},
name: "storm/formatter/svg",
rules: {
"format/prettier": [
"error",
mergePrettierOptions({
...prettierXmlOptions,
...prettierOptions
}, {
parser: "xml",
plugins: [
"@prettier/plugin-xml"
]
})
]
}
});
}
if (options.markdown) {
const formater = options.markdown === true ? "prettier" : options.markdown;
configs3.push({
files: [
GLOB_MARKDOWN
],
languageOptions: {
parser: parserPlain
},
name: "storm/formatter/markdown",
rules: {
[`format/${formater}`]: [
"error",
formater === "prettier" ? mergePrettierOptions(prettierOptions, {
embeddedLanguageFormatting: "off",
parser: "markdown"
}) : {
...dprintOptions,
language: "markdown"
}
]
}
});
}
if (options.astro) {
configs3.push({
files: [
GLOB_ASTRO
],
languageOptions: {
parser: parserPlain
},
name: "storm/formatter/astro",
rules: {
"format/prettier": [
"error",
mergePrettierOptions(prettierOptions, {
parser: "astro",
plugins: [
"prettier-plugin-astro"
]
})
]
}
});
configs3.push({
files: [
GLOB_ASTRO,
GLOB_ASTRO_TS
],
name: "storm/formatter/astro/disables",
rules: {
"style/arrow-parens": "off",
"style/block-spacing": "off",
"style/comma-dangle": "off",
"style/indent": "off",
"style/no-multi-spaces": "off",
"style/quotes": "off",
"style/semi": "off"
}
});
}
if (options.graphql) {
configs3.push({
files: [
GLOB_GRAPHQL
],
languageOptions: {
parser: parserPlain
},
name: "storm/formatter/graphql",
rules: {
"format/prettier": [
"error",
mergePrettierOptions(prettierOptions, {
parser: "graphql"
})
]
}
});
}
return configs3;
}
__name(formatters, "formatters");
// src/configs/graphql.ts
async function graphql(options = {}) {
const { relay = true, operations = true, schema = true, overrides = {} } = options;
await ensurePackages([
"@graphql-eslint/eslint-plugin",
"eslint-plugin-relay"
]);
const [pluginGraphQL, pluginRelay] = await Promise.all([
interopDefault(import("@graphql-eslint/eslint-plugin")),
interopDefault(import("eslint-plugin-relay"))
]);
return [
{
name: "storm/graphql/setup",
files: [
"**/*.graphql",
"**/*.gql"
],
languageOptions: {
parser: pluginGraphQL.parser
},
plugins: {
"@graphql-eslint": pluginGraphQL
}
},
{
name: "storm/graphql/rules",
plugins: {
graphql: pluginGraphQL
},
rules: {
...schema ? {
"@graphql-eslint/description-style": "error",
"@graphql-eslint/known-argument-names": "error",
"@graphql-eslint/known-directives": "error",
"@graphql-eslint/known-type-names": "error",
"@graphql-eslint/lone-schema-definition": "error",
"@graphql-eslint/naming-convention": [
"error",
{
types: "PascalCase",
FieldDefinition: "camelCase",
InputValueDefinition: "camelCase",
Argument: "camelCase",
DirectiveDefinition: "camelCase",
EnumValueDefinition: "UPPER_CASE",
"FieldDefinition[parent.name.value=Query]": {
forbiddenPrefixes: [
"query",
"get"
],
forbiddenSuffixes: [
"Query"
]
},
"FieldDefinition[parent.name.value=Mutation]": {
forbiddenPrefixes: [
"mutation"
],
forbiddenSuffixes: [
"Mutation"
]
},
"FieldDefinition[parent.name.value=Subscription]": {
forbiddenPrefixes: [
"subscription"
],
forbiddenSuffixes: [
"Subscription"
]
},
"EnumTypeDefinition,EnumTypeExtension": {
forbiddenPrefixes: [
"Enum"
],
forbiddenSuffixes: [
"Enum"
]
},
"InterfaceTypeDefinition,InterfaceTypeExtension": {
forbiddenPrefixes: [
"Interface"
],
forbiddenSuffixes: [
"Interface"
]
},
"UnionTypeDefinition,UnionTypeExtension": {
forbiddenPrefixes: [
"Union"
],
forbiddenSuffixes: [
"Union"
]
},
"ObjectTypeDefinition,ObjectTypeExtension": {
forbiddenPrefixes: [
"Type"
],
forbiddenSuffixes: [
"Type"
]
}
}
],
"@graphql-eslint/no-hashtag-description": "error",
"@graphql-eslint/no-typename-prefix": "error",
"@graphql-eslint/no-unreachable-types": "error",
"@graphql-eslint/possible-type-extension": "error",
"@graphql-eslint/provided-required-arguments": "error",
"@graphql-eslint/require-deprecation-reason": "error",
"@graphql-eslint/require-description": [
"error",
{
types: true,
DirectiveDefinition: true,
rootField: true
}
],
"@graphql-eslint/strict-id-in-types": "error",
"@graphql-eslint/unique-directive-names": "error",
"@graphql-eslint/unique-directive-names-per-location": "error",
"@graphql-eslint/unique-enum-value-names": "error",
"@graphql-eslint/unique-field-definition-names": "error",
"@graphql-eslint/unique-operation-types": "error",
"@graphql-eslint/unique-type-names": "error"
} : {},
...operations ? {
"@graphql-eslint/executable-definitions": "error",
"@graphql-eslint/fields-on-correct-type": "error",
"@graphql-eslint/fragments-on-composite-type": "error",
"@graphql-eslint/known-argument-names": "error",
"@graphql-eslint/known-directives": "error",
"@graphql-eslint/known-fragment-names": "error",
"@graphql-eslint/known-type-names": "error",
"@graphql-eslint/lone-anonymous-operation": "error",
"@graphql-eslint/naming-convention": [
"error",
{
VariableDefinition: "camelCase",
OperationDefinition: {
style: "PascalCase",
forbiddenPrefixes: [
"Query",
"Mutation",
"Subscription",
"Get"
],
forbiddenSuffixes: [
"Query",
"Mutation",
"Subscription"
]
},
FragmentDefinition: {
style: "PascalCase",
forbiddenPrefixes: [
"Fragment"
],
forbiddenSuffixes: [
"Fragment"
]
}
}
],
"@graphql-eslint/no-anonymous-operations": "error",
"@graphql-eslint/no-deprecated": "error",
"@graphql-eslint/no-duplicate-fields": "error",
"@graphql-eslint/no-fragment-cycles": "error",
"@graphql-eslint/no-undefined-variables": "error",
"@graphql-eslint/no-unused-fragments": "error",
"@graphql-eslint/no-unused-variables": "error",
"@graphql-eslint/one-field-subscriptions": "error",
"@graphql-eslint/overlapping-fields-can-be-merged": "error",
"@graphql-eslint/possible-fragment-spread": "error",
"@graphql-eslint/provided-required-arguments": "error",
"@graphql-eslint/require-selections": "error",
"@graphql-eslint/scalar-leafs": "error",
"@graphql-eslint/selection-set-depth": [
"error",
{
maxDepth: 7
}
],
"@graphql-eslint/unique-argument-names": "error",
"@graphql-eslint/unique-directive-names-per-location": "error",
"@graphql-eslint/unique-fragment-name": "error",
"@graphql-eslint/unique-input-field-names": "error",
"@graphql-eslint/unique-operation-name": "error",
"@graphql-eslint/unique-variable-names": "error",
"@graphql-eslint/value-literals-of-correct-type": "error",
"@graphql-eslint/variables-are-input-types": "error",
"@graphql-eslint/variables-in-allowed-position": "error"
} : {},
...overrides
}
},
relay ? {
name: "storm/graphql/relay",
plugins: {
relay: pluginRelay
},
rules: {
// errors
"relay/graphql-syntax": "error",
"relay/graphql-naming": "error",
// warnings
"relay/compat-uses-vars": "warn",
"relay/generated-typescript-types": "warn",
"relay/no-future-added-value": "warn",
"relay/unused-fields": "warn",
"relay/must-colocate-fragment-spreads": "warn",
"relay/function-required-argument": "warn",
"relay/hook-required-argument": "warn",
// @graphql-eslint rules
"@graphql-eslint/relay-arguments": "error",
"@graphql-eslint/relay-connection-types": "error",
"@graphql-eslint/relay-edge-types": "error",
"@graphql-eslint/relay-page-info": "error"
}
} : {}
];
}
__name(graphql, "graphql");
// src/configs/ignores.ts
async function ignores(userIgnores = []) {
return [
{
ignores: [
...GLOB_EXCLUDE,
...userIgnores
],
name: "storm/ignores"
}
];
}
__name(ignores, "ignores");
// src/plugins.ts
import { default as default2 } from "@cspell/eslint-plugin";
import { default as default3 } from "@nx/eslint-plugin/nx.js";
import * as pluginImport from "eslint-plugin-import-x";
import { default as default4 } from "eslint-plugin-n";
import { default as default5 } from "eslint-plugin-no-secrets";
import { default as default6 } from "eslint-plugin-perfectionist";
// ../../node_modules/.pnpm/eslint-plugin-pnpm@0.3.0_patch_hash=72dcde755c336eeca3e6170de1106fd85ecb66171e060788f80_c181f521aad000c211a6aa41fe87e6e0/node_modules/eslint-plugin-pnpm/dist/index.mjs
import * as jsoncParser from "jsonc-eslint-parser";
import * as yamlParser from "yaml-eslint-parser";
import fs, { existsSync, readFileSync } from "node:fs";
import process from "node:process";
import { findUpSync } from "find-up-simple";
import { parsePnpmWorkspaceYaml } from "pnpm-workspace-yaml";
import { basename, normalize, resolve, dirname } from "pathe";
import { globSync } from "tinyglobby";
var name = "eslint-plugin-pnpm";
var version = "0.3.0";
var blobUrl = "https://github.com/antfu/eslint-plugin-pnpm/blob/main/src/rules/";
function RuleCreator(urlCreator) {
return /* @__PURE__ */ __name(function createNamedRule({
name: name2,
meta,
...rule
}) {
return createRule({
meta: {
...meta,
docs: {
...meta.docs,
url: urlCreator(name2)
}
},
...rule
});
}, "createNamedRule");
}
__name(RuleCreator, "RuleCreator");
function createRule({
create,
defaultOptions,
meta
}) {
return {
create: /* @__PURE__ */ __name((context) => {
const optionsWithDefault = context.options.map((options, index) => {
return {
...defaultOptions[index] || {},
...options || {}
};
});
return create(context, optionsWithDefault);
}, "create"),
defaultOptions,
meta
};
}
__name(createRule, "createRule");
var createEslintRule = RuleCreator(
(ruleName) => `${blobUrl}${ruleName}.test.ts`
);
function getPackageJsonRootNode(context) {
if (!context.filename.endsWith("package.json"))
return;
const ast = context.sourceCode.ast;
const root = ast.body[0];
if (root.expression.type === "JSONObjectExpression")
return root.expression;
}
__name(getPackageJsonRootNode, "getPackageJsonRootNode");
function* iterateDependencies(context, fields) {
const root = getPackageJsonRootNode(context);
if (!root)
return;
for (const fieldName of fields) {
const path = fieldName.split(".");
let node2 = root;
for (let i = 0; i < path.length; i++) {
const item = node2.properties.find((property) => property.key.type === "JSONLiteral" && property.key.value === path[i]);
if (!item?.value || item.value.type !== "JSONObjectExpression") {
node2 = void 0;
break;
}
node2 = item.value;
}
if (!node2 || node2 === root)
continue;
for (const property of node2.properties) {
if (property.value.type !== "JSONLiteral" || property.key.type !== "JSONLiteral")
continue;
if (typeof property.value.value !== "string")
continue;
const packageName = String(property.key.value);
const specifier = String(property.value.value);
yield {
packageName,
specifier,
property
};
}
}
}
__name(iterateDependencies, "iterateDependencies");
function readPnpmWorkspace() {
const filepath = findUpSync("pnpm-workspace.yaml", { cwd: process.cwd() });
if (!filepath)
throw new Error("pnpm-workspace.yaml not found");
const content = fs.readFileSync(filepath, "utf-8");
const workspace2 = parsePnpmWorkspaceYaml(content);
let queueTimer;
const queue = [];
const write = /* @__PURE__ */ __name(() => {
fs.writeFileSync(filepath, workspace2.toString());
}, "write");
const hasQueue = /* @__PURE__ */ __name(() => queueTimer != null, "hasQueue");
const queueChange = /* @__PURE__ */ __name((fn, order) => {
if (order === "pre")
queue.unshift(fn);
else
queue.push(fn);
if (queueTimer != null)
clearTimeout(queueTimer);
queueTimer = setTimeout(() => {
queueTimer = void 0;
const clone = [...queue];
queue.length = 0;
for (const fn2 of clone)
fn2(workspace2);
if (workspace2.hasChanged())
write();
}, 1e3);
}, "queueChange");
return {
filepath,
...workspace2,
hasQueue,
queueChange
};
}
__name(readPnpmWorkspace, "readPnpmWorkspace");
var WORKSPACE_CACHE_TIME = 1e4;
var workspaceLastRead;
var workspace;
function getPnpmWorkspace() {
if (workspaceLastRead && workspace && !workspace.hasQueue() && Date.now() - workspaceLastRead > WORKSPACE_CACHE_TIME) {
workspace = void 0;
}
if (!workspace) {
workspace = readPnpmWorkspace();
workspaceLastRead = Date.now();
}
return workspace;
}
__name(getPnpmWorkspace, "getPnpmWorkspace");
var RULE_NAME$4 = "json-enforce-catalog";
var DEFAULT_FIELDS$1 = [
"dependencies",
"devDependencies"
];
var IGNORED_DEPENDENCIES = [
"typescript"
];
var enforceCatalog = createEslintRule({
name: RULE_NAME$4,
meta: {
type: "layout",
docs: {
description: 'Enforce using "catalog:" in `package.json`'
},
fixable: "code",
schema: [
{
type: "object",
properties: {
allowedProtocols: {
type: "array",
description: "Allowed protocols in specifier to not be converted to catalog",
items: {
type: "string"
}
},
autofix: {
type: "boolean",
description: "Whether to autofix the linting error",
default: true
},
defaultCatalog: {
type: "string",
description: "Default catalog to use when moving version to catalog with autofix"
},
reuseExistingCatalog: {
type: "boolean",
description: "Whether to reuse existing catalog when moving version to catalog with autofix",
default: true
},
conflicts: {
type: "string",
description: "Strategy to handle conflicts when adding packages to catalogs",
enum: ["new-catalog", "overrides", "error"],
default: "new-catalog"
},
fields: {
type: "array",
description: "Fields to check for catalog",
items: { type: "string" },
default: DEFAULT_FIELDS$1
},
ignore: {
type: "array",
description: "A list of dependencies to ignore",
items: { type: "string" },
default: IGNORED_DEPENDENCIES
}
},
additionalProperties: false
}
],
messages: {
expectCatalog: 'Expect to use catalog instead of plain specifier, got "{{specifier}}" for package "{{packageName}}".'
}
},
defaultOptions: [{}],
create(context, [options]) {
const {
allowedProtocols = ["workspace", "link", "file"],
defaultCatalog = "default",
autofix = true,
reuseExistingCatalog = true,
conflicts = "new-catalog",
fields = DEFAULT_FIELDS$1,
ignore = IGNORED_DEPENDENCIES
} = options || {};
for (const { packageName, specifier, property } of iterateDependencies(context, fields)) {
if (ignore?.some((i) => i === packageName))
continue;
if (specifier.startsWith("catalog:"))
continue;
if (allowedProtocols?.some((p) => specifier.startsWith(p)))
continue;
const workspace2 = getPnpmWorkspace();
if (!workspace2)
return {};
let targetCatalog = reuseExistingCatalog ? workspace2.getPackageCatalogs(packageName)[0] || defaultCatalog : defaultCatalog;
const resolvedConflicts = workspace2.hasSpecifierConflicts(
targetCatalog,
packageName,
specifier
);
let shouldFix = autofix;
if (conflicts === "error") {
if (resolvedConflicts.conflicts) {
shouldFix = false;
}
}
if (conflicts === "new-catalog" && resolvedConflicts.conflicts) {
targetCatalog = resolvedConflicts.newCatalogName;
}
context.report({
node: property.value,
messageId: "expectCatalog",
data: {
specifier,
packageName
},
fix: shouldFix ? (fixer) => {
workspace2.queueChange(() => {
workspace2.setPackage(targetCatalog, packageName, specifier);
});
return fixer.replaceText(
property.value,
targetCatalog === "default" ? JSON.stringify("catalog:") : JSON.stringify(`catalog:${targetCatalog}`)
);
} : void 0
});
}
return {};
}
});
var RULE_NAME$3 = "json-prefer-workspace-settings";
var preferWorkspaceSettings = createEslintRule({
name: RULE_NAME$3,
meta: {
type: "layout",
docs: {
description: "Prefer having pnpm settings in `pnpm-workspace.yaml` instead of `package.json`. This would requires pnpm v10.6+, see https://github.com/orgs/pnpm/discussions/9037."
},
fixable: "code",
schema: [
{
type: "object",
properties: {
autofix: {
type: "boolean",
description: "Whether to autofix the linting error",
default: true
}
},
additionalProperties: false
}
],
messages: {
unexpectedPnpmSettings: "Unexpected pnpm settings in package.json, should move to pnpm-workspace.yaml"
}
},
defaultOptions: [{}],
create(context, [options = {}]) {
const {
autofix = true
} = options || {};
const root = getPackageJsonRootNode(context);
if (!root)
return {};
const pnpmNode = root.properties.find((property) => property.key.type === "JSONLiteral" && property.key.value === "pnpm");
if (!pnpmNode)
return {};
const workspace2 = getPnpmWorkspace();
if (!workspace2)
return {};
context.report({
node: pnpmNode,
messageId: "unexpectedPnpmSettings",
fix: autofix ? (fixer) => {
const json = JSON.parse(context.sourceCode.text);
const pnpmSettings = json.pnpm;
const flatValueParis = [];
function traverse(value, paths) {
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
for (const key in value) {
traverse(value[key], [...paths, key]);
}
} else {
flatValueParis.push([paths, value]);
}
}
__name(traverse, "traverse");
traverse(pnpmSettings, []);
workspace2.queueChange(() => {
for (const [paths, value] of flatValueParis) {
workspace2.setPath(paths, value);
}
});
let start = pnpmNode.range[0];
let end = pnpmNode.range[1];
const before = context.sourceCode.getTokenBefore(pnpmNode);
if (before)
start = before.range[1];
const after = context.sourceCode.getTokenAfter(pnpmNode);
if (after?.type === "Punctuator" && after.value === ",")
end = after.range[1];
return fixer.removeRange([start, end]);
} : void 0
});
return {};
}
});
var RULE_NAME$2 = "json-valid-catalog";
var DEFAULT_FIELDS = [
"dependencies",
"devDependencies",
"optionalDependencies",
"peerDependencies",
"resolutions",
"overrides",
"pnpm.overrides"
];
var validCatalog = createEslintRule({
name: RULE_NAME$2,
meta: {
type: "layout",
docs: {
description: "Enforce using valid catalog in `package.json`"
},
fixable: "code",
schema: [
{
type: "object",
properties: {
autoInsert: {
type: "boolean",
description: "Whether to auto insert to catalog if missing",
default: true
},
autoInsertDefaultSpecifier: {
type: "string",
description: "Default specifier to use when auto inserting to catalog",
default: "^0.0.0"
},
autofix: {
type: "boolean",
description: "Whether to autofix the linting error",
default: true
},
enforceNoConflict: {
type: "boolean",
description: "Whether to enforce no conflicts when adding packages to catalogs (will create version-specific catalogs)",
default: true
},
fields: {
type: "array",
description: "Fields to check for catalog",
default: DEFAULT_FIELDS
}
},
additionalProperties: false
}
],
messages: {
invalidCatalog: 'Catalog "{{specifier}}" for package "{{packageName}}" is not defined in `pnpm-workspace.yaml`.'
}
},
defaultOptions: [{}],
create(context, [options = {}]) {
const {
autoInsert = true,
autofix = true,
autoInsertDefaultSpecifier = "^0.0.0",
fields = DEFAULT_FIELDS
} = options || {};
for (const { packageName, specifier, property } of iterateDependencies(context, fields)) {
if (!specifier.startsWith("catalog:"))
continue;
const workspace2 = getPnpmWorkspace();
if (!workspace2)
return {};
const currentCatalog = specifier.replace(/^catalog:/, "").trim() || "default";
const existingCatalogs = workspace2.getPackageCatalogs(packageName);
if (!existingCatalogs.includes(currentCatalog)) {
context.report({
node: property.value,
messageId: "invalidCatalog",
data: {
specifier,
packageName
},
fix: !autofix || !autoInsert && !existingCatalogs.length ? void 0 : (fixer) => {
let catalog = existingCatalogs[0];
if (!catalog && autoInsert) {
catalog = currentCatalog;
workspace2.queueChange(() => {
workspace2.setPackage(catalog, packageName, autoInsertDefaultSpecifier);
}, "pre");
}
return fixer.replaceText(
property.value,
catalog === "default" ? JSON.stringify("catalog:") : JSON.stringify(`catalog:${catalog}`)
);
}
});
}
}
return {};
}
});
var rules$2 = {
"json-enforce-catalog": enforceCatalog,
"json-valid-catalog": validCatalog,
"json-prefer-workspace-settings": preferWorkspaceSettings
};
var RULE_NAME$1 = "yaml-no-duplicate-catalog-item";
var noDuplicateCatalogItem = createEslintRule({
name: RULE_NAME$1,
meta: {
type: "problem",
docs: {
description: "Disallow unused catalogs in `pnpm-workspace.yaml`"
},
fixable: "code",
schema: [
{
type: "object",
properties: {
allow: {
type: "array",
items: { type: "string" }
}
},
additionalProperties: false
}
],
messages: {
duplicateCatalogItem: 'Catalog item "{{name}}" is already defined in the "{{existingCatalog}}" catalog. You may want to remove one of them.'
}
},
defaultOptions: [{}],
create(context, [options = {}]) {
if (basename(context.filename) !== "pnpm-workspace.yaml")
return {};
const workspace2 = getPnpmWorkspace();
if (!workspace2 || normalize(workspace2.filepath) !== normalize(context.filename))
return {};
if (workspace2.hasChanged() || workspace2.hasQueue())
return {};
const { allow = [] } = options;
workspace2.setContent(context.sourceCode.text);
const json = workspace2.toJSON() || {};
const exists = /* @__PURE__ */ new Map();
const catalogs = {
...json.catalogs,
default: json.catalog
};
const doc = workspace2.getDocument();
for (const [catalog, object] of Object.entries(catalogs)) {
if (!object)
continue;
for (const key of Object.keys(object)) {
if (allow.includes(key))
continue;
if (exists.has(key)) {
const existingCatalog = exists.get(key);
const node2 = doc.getIn(catalog === "default" ? ["catalog", key] : ["catalogs", catalog, key], true);
const start = context.sourceCode.getLocFromIndex(node2.range[0]);
const end = context.sourceCode.getLocFromIndex(node2.range[1]);
context.report({
loc: {
start,
end
},
messageId: "duplicateCatalogItem",
data: {
name: key,
currentCatalog: catalog,
existingCatalog
}
});
} else {
exists.set(key, catalog);
}
}
}
return {};
}
});
var RULE_NAME = "yaml-no-unused-catalog-item";
var noUnusedCatalogItem = createEslintRule({
name: RULE_NAME,
meta: {
type: "problem",
docs: {
description: "Disallow unused catalogs in `pnpm-workspace.yaml`"
},
fixable: "code",
schema: [],
messages: {
unusedCatalogItem: 'Catalog item "{{catalogItem}}" is not used in any package.json.'
}
},
defaultOptions: [],
create(context) {
if (basename(context.filename) !== "pnpm-workspace.yaml")
return {};
const workspace2 = getPnpmWorkspace();
if (!workspace2 || normalize(workspace2.filepath) !== normalize(context.filename))
return {};
if (workspace2.hasChanged() || workspace2.hasQueue())
return {};
workspace2.setContent(context.sourceCode.text);
const parsed = workspace2.toJSON() || {};
const root = resolve(dirname(context.filename));
const entries = /* @__PURE__ */ new Map();
const doc = workspace2.getDocument();
const catalogs = {
default: doc.getIn(["catalog"])
};
for (const item of doc.getIn(["catalogs"])?.items || [])
catalogs[String(item.key)] = item.value;
for (const [catalog, map] of Object.entries(catalogs)) {
if (!map)
continue;
for (const item of map.items) {
entries.set(`${String(item.key)}:${catalog}`, item);
}
}
if (entries.size === 0)
return {};
const dirs = parsed.packages ? globSync(parsed.packages, {
cwd: root,
dot: false,
ignore: [
"**/node_modules/**",
"**/dist/**",
"**/build/**",
"**/dist/**",
"**/dist/**"
],
absolute: true,
expandDirectories: false,
onlyDirectories: true
}) : [];
dirs.push(root);
const packages = dirs.map((dir) => resolve(dir, "package.json")).filter((x) => existsSync(x)).sort();
const FIELDS = [
"dependencies",
"devDependencies",
"peerDependencies",
"optionalDependencies",
"overrides",
"resolutions",
"pnpm.overrides"
];
for (const path of packages) {
const pkg = JSON.parse(readFileSync(path, "utf-8"));
for (const field of FIELDS) {
const map = getObjectPath(pkg, field.split("."));
if (!map)
continue;
for (const [name2, value] of Object.entries(map)) {
if (!value.startsWith("catalog:"))
continue;
const catalog = value.slice(8) || "default";
const key = `${name2}:${catalog}`;
entries.delete(key);
}
}
}
if (entries.size > 0) {
for (const [key, value] of Array.from(entries.entries()).sort((a, b) => a[0].localeCompare(b[0]))) {
const start = context.sourceCode.getLocFromIndex(value.key.range[0]);
const end = context.sourceCode.getLocFromIndex(value.value.range.at(-1));
context.report({
loc: {
start,
end
},
messageId: "unusedCatalogItem",
data: { catalogItem: key }
});
}
}
return {};
}
});
function getObjectPath(obj, path) {
let current = obj;
for (const key of path) {
current = current[key];
if (!current)
return void 0;
}
return current;
}
__name(getObjectPath, "getObjectPath");
var rules$1 = {
"yaml-no-unused-catalog-item": noUnusedCatalogItem,
"yaml-no-duplicate-catalog-item": noDuplicateCatalogItem
};
var rules = {
...rules$2,
...rules$1
};
var plugin = {
meta: {
name,
version
},
rules
};
var configsJson = [
{
name: "pnpm/package.json",
files: [
"package.json",
"**/package.json"
],
languageOptions: {
parser: jsoncParser
},
plugins: {
pnpm: plugin
},
rules: {
"pnpm/json-enforce-catalog": "error",
"pnpm/json-valid-catalog": "error",
"pnpm/json-prefer-workspace-settings": "error"
}
}
];
var configsYaml = [
{
name: "pnpm/pnpm-workspace-yaml",
files: ["pnpm-workspace.yaml"],
languageOptions: {
parser: yamlParser
},
plugins: {
pnpm: plugin
},
rules: {
"pnpm/yaml-no-unused-catalog-item": "error",
"pnpm/yaml-no-duplicate-catalog-item": "error"
}
}
];
var configs = {
recommended: [
...configsJson
// Yaml support is still experimental
// ...configsYaml,
],
json: configsJson,
yaml: configsYaml
};
plugin.configs = configs;
// src/plugins.ts
import { default as default7 } from "eslint-plugin-prettier";
import { default as default8 } from "eslint-plugin-unicorn";
import { default as default9 } from "eslint-plugin-unused-imports";
// src/configs/imports.ts
async function imports(options = {}) {
const { stylistic: stylistic2 = true } = options;
return [
{
name: "storm/imports/rules",
plugins: {
import: pluginImport
},
rules: {
"import/consistent-type-specifier-style": [
"error",
"prefer-top-level"
],
"import/first": "off",
"import/no-duplicates": "error",
"import/no-mutable-exports": "error",
"import/no-named-default": "error",
"import/no-self-import": "error",
"import/no-webpack-loader-syntax": "error",
...stylistic2 ? {
"import/newline-after-import": [
"error",
{
count: 1
}
]
} : {}
}
}
];
}
__name(imports, "imports");
// src/configs/javascript.ts
import defu2 from "defu";
import globalsLib from "globals";
async function javascript(options = {}) {
const { lineEndings = "unix", overrides = {}, repositoryName, globals = {} } = options;
return [
{
name: "storm/javascript/setup",
languageOptions: {
ecmaVersion: 2022,
globals: defu2(globals, {
...globalsLib.browser,
...globalsLib.es2021,
...globalsLib.node,
document: "readonly",
navigator: "readonly",
window: "readonly"
}),
parserOptions: {
ecmaFeatures: {
jsx: true
},
ecmaVersion: 2022
}
},
linterOptions: {
reportUnusedDisableDirectives: true
}
},
{
// Banner
...banner_plugin_default.configs?.["recommended"],
name: "storm/javascript/banner",
plugins: {
banner: banner_plugin_default
},
rules: {
"banner/banner": [
"error",
{
commentType: "block",
numNewlines: 2,
repositoryName,
lineEndings
}
]
}
},
{
name: "storm/javascript/rules",
plugins: {
"unused-imports": default9
},
rules: {
// disallow use of console
"no-console": "error",
// Disallows expressions where the operation doesn't affect the value
// https://eslint.org/docs/rules/no-constant-binary-expression
// TODO: semver-major, enable
"no-constant-binary-expression": "off",
// disallow use of constant expressions in conditions
"no-constant-condition": "warn",
// disallow control characters in regular expressions
"no-control-regex": "error",
// disallow use of debugger
"no-debugger": "warn",
// disallow duplicate arguments in functions
"no-dupe-args": "error",
// Disallow duplicate conditions in if-else-if chains
// https://eslint.org/docs/rules/no-dupe-else-if
"no-dupe-else-if": "error",
// disallow duplicate keys when creating object literals
"no-dupe-keys": "error",
// disallow a duplicate case label.
"no-duplicate-case": "error",
// disallow empty statements
"no-empty": "error",
// disallow the use of empty character classes in regular expressions
"no-empty-character-class": "error",
// disallow assigning to the exception in a catch block
"no-ex-assign": "error",
// disallow double-negation boolean casts in a boolean context
// https://eslint.org/docs/rules/no-extra-boolean-cast
"no-extra-boolean-cast": "error",
// disallow unnecessary parentheses
// https://eslint.org/docs/rules/no-extra-parens
"no-extra-parens": [
"off",
"all",
{
conditionalAssign: true,
nestedBinaryExpressions: false,
returnAssign: false,
ignoreJSX: "all",
enforceForArrowConditionals: false
}
],
// disallow unnecessary semicolons
"no-extra-semi": "error",
// disallow overwriting functions written as function declarations
"no-func-assign": "off",
// https://eslint.org/docs/rules/no-import-assign
"no-import-assign": "error",
// disallow function or variable declarations in nested blocks
"no-inner-declarations": "warn",
// disallow invalid regular expression strings in the RegExp constructor
"no-invalid-regexp": "error",
// disallow irregular whitespace outside of strings and comments
"no-irregular-whitespace": "error",
// Disallow Number Literals That Lose Precision
// https://eslint.org/docs/rules/no-loss-of-precision
"no-loss-of-precision": "error",
// Disallow characters which are made with multiple code points in character class syntax
// https://eslint.org/docs/rules/no-misleading-character-class
"no-misleading-character-class": "error",
// disallow the use of object properties of the global object (Math and JSON) as functions
"no-obj-calls": "error",
// Disallow new operators with global non-constructor functions
// https://eslint.org/docs/latest/rules/no-new-native-nonconstructor
// TODO: semver-major, enable
"no-new-native-nonconstructor": "off",
// Disallow returning values from Promise executor functions
// https://eslint.org/docs/rules/no-promise-executor-return
"no-promise-executor-return": "error",
// disallow use of Object.prototypes builtins directly
// https://eslint.org/docs/rules/no-prototype-builtins
"no-prototype-builtins": "error",
// disallow multiple spaces in a regular expression literal
"no-regex-spaces": "error",
// Disallow returning values from setters
// https://eslint.org/docs/rules/no-setter-return
"no-setter-return": "error",
// disallow sparse arrays
"no-sparse-arrays": "error",
// Disallow template literal placeholder syntax in regular strings
// https://eslint.org/docs/rules/no-template-curly-in-string
"no-template-curly-in-string": "error",
// Avoid code that looks like two expressions but is actually one
// https://eslint.org/docs/rules/no-unexpected-multiline
"no-unexpected-multiline": "error",
// disallow unreachable statements after a return, throw, continue, or break statement
"no-unreachable": "error",
// Disallow loops with a body that allows only one iteration
// https://eslint.org/docs/rules/no-unreachable-loop
"no-unreachable-loop": [
"error",
{
ignore: []
// WhileStatement, DoWhileStatement, ForStatement, ForInStatement, ForOfStatement
}
],
// disallow return/throw/break/continue inside finally blocks
// https://eslint.org/docs/rules/no-unsafe-finally
"no-unsafe-finally": "error",
// disallow negating the left operand of relational operators
// https://eslint.org/docs/rules/no-unsafe-negation
"no-unsafe-negation": "error",
// disallow use of optional chaining in contexts where the undefined value is not allowed
// https://eslint.org/docs/rules/no-unsafe-optional-chaining
"no-unsafe-optional-chaining": [
"error",
{
disallowArithmeti