eslint-plugin-ts-data-forge 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs.map +1 -1
- package/dist/rules/import-utils.mjs +1 -1
- package/dist/rules/import-utils.mjs.map +1 -1
- package/dist/rules/no-unnecessary-type-guard.mjs.map +1 -1
- package/dist/rules/prefer-canonical-array-slicing.d.mts.map +1 -1
- package/dist/rules/prefer-canonical-array-slicing.mjs +4 -4
- package/dist/rules/prefer-canonical-array-slicing.mjs.map +1 -1
- package/dist/rules/prefer-canonical-length-guard.d.mts.map +1 -1
- package/dist/rules/prefer-canonical-length-guard.mjs.map +1 -1
- package/dist/rules/prefer-comparison-over-nullish-guard.mjs.map +1 -1
- package/package.json +3 -3
- package/src/plugin.mts +4 -2
- package/src/rules/import-utils.mts +1 -1
- package/src/rules/no-unnecessary-type-guard.mts +6 -6
- package/src/rules/prefer-canonical-array-slicing.mts +8 -7
- package/src/rules/prefer-canonical-length-guard.mts +3 -2
- package/src/rules/prefer-comparison-over-nullish-guard.mts +2 -2
package/dist/plugin.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.mts","sourceRoot":"","sources":["../src/plugin.mts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"plugin.d.mts","sourceRoot":"","sources":["../src/plugin.mts"],"names":[],"mappings":"AAEA,OAAO,EAAyB,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AA2CvE,eAAO,MAAM,uBAAuB;;iBAEhC,IAAI,EAAE,6BAA6B;;;;;;;;;;;;;;;;;;;;QAInC,oDAAoD;iBACpD,WAAW;2BAtBP,2BAA2B;;yBAQ3B,eAAe,EAAI,YAAY;;;yEA3BW,OAAO;uEACT,OAAO;wEACN,OAAO;8DACjB,OAAO;yDACZ,OAAO;wDACR,OAAO;oEACK,OAAO;gEACX,OAAO;uEACA,OAAO;oEACV,OAAO;sEACL,OAAO;oEACT,OAAO;+EACI,OAAO;;;;CA+B9B,CAAC"}
|
package/dist/plugin.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.mjs","sources":["../src/plugin.mts"],"sourcesContent":["import { tsDataForgeRules } from './rules/index.mjs';\nimport { type ESLintFlatConfig, type ESLintPlugin } from './types.mjs';\n\n/**\n * Every rule this plugin ships, at `error`.\n *\n * The `satisfies` clause below is keyed off {@link tsDataForgeRules}, so adding\n * a rule without listing it here fails to type-check.\n */\nconst recommendedRules = {\n 'ts-data-forge/prefer-canonical-array-slicing': 'error',\n 'ts-data-forge/prefer-canonical-length-cast': 'error',\n 'ts-data-forge/prefer-canonical-length-guard': 'error',\n 'ts-data-forge/prefer-arr-is-array': 'error',\n 'ts-data-forge/prefer-arr-sum': 'error',\n 'ts-data-forge/prefer-as-int': 'error',\n 'ts-data-forge/prefer-is-non-null-object': 'error',\n 'ts-data-forge/prefer-range-for-loop': 'error',\n 'ts-data-forge/prefer-is-record-and-has-key': 'error',\n 'ts-data-forge/prefer-num-safe-parse-int': 'error',\n 'ts-data-forge/prefer-num-safe-parse-float': 'error',\n 'ts-data-forge/no-unnecessary-type-guard': 'error',\n 'ts-data-forge/prefer-comparison-over-nullish-guard': 'error',\n} as const satisfies
|
|
1
|
+
{"version":3,"file":"plugin.mjs","sources":["../src/plugin.mts"],"sourcesContent":["import { type ReadonlyRecord } from 'ts-type-forge';\nimport { tsDataForgeRules } from './rules/index.mjs';\nimport { type ESLintFlatConfig, type ESLintPlugin } from './types.mjs';\n\n/**\n * Every rule this plugin ships, at `error`.\n *\n * The `satisfies` clause below is keyed off {@link tsDataForgeRules}, so adding\n * a rule without listing it here fails to type-check.\n */\nconst recommendedRules = {\n 'ts-data-forge/prefer-canonical-array-slicing': 'error',\n 'ts-data-forge/prefer-canonical-length-cast': 'error',\n 'ts-data-forge/prefer-canonical-length-guard': 'error',\n 'ts-data-forge/prefer-arr-is-array': 'error',\n 'ts-data-forge/prefer-arr-sum': 'error',\n 'ts-data-forge/prefer-as-int': 'error',\n 'ts-data-forge/prefer-is-non-null-object': 'error',\n 'ts-data-forge/prefer-range-for-loop': 'error',\n 'ts-data-forge/prefer-is-record-and-has-key': 'error',\n 'ts-data-forge/prefer-num-safe-parse-int': 'error',\n 'ts-data-forge/prefer-num-safe-parse-float': 'error',\n 'ts-data-forge/no-unnecessary-type-guard': 'error',\n 'ts-data-forge/prefer-comparison-over-nullish-guard': 'error',\n} as const satisfies ReadonlyRecord<\n `ts-data-forge/${keyof typeof tsDataForgeRules}`,\n 'error'\n>;\n\nconst recommendedConfig = {\n name: 'ts-data-forge/recommended',\n plugins: {\n // Resolved lazily so that this config registers the *same* object that\n // `eslintPluginTsDataForge` refers to. ESLint rejects a plugin name that\n // maps to two different objects with `Cannot redefine plugin`, which is\n // what a user combining this preset with their own\n // `plugins: { 'ts-data-forge': eslintPluginTsDataForge }` entry would hit\n // if the preset embedded a separate copy of the plugin.\n get 'ts-data-forge'(): ESLintPlugin {\n return eslintPluginTsDataForge;\n },\n },\n rules: recommendedRules,\n} as const satisfies ESLintFlatConfig;\n\nexport const eslintPluginTsDataForge = {\n meta: {\n name: 'eslint-plugin-ts-data-forge',\n },\n rules: tsDataForgeRules,\n configs: {\n /** Enables every rule of this plugin at `error`. */\n recommended: recommendedConfig,\n },\n} as const satisfies ESLintPlugin;\n"],"names":[],"mappings":";;AAUA,MAAM,gBAAA,GAAmB;AAAA,EACvB,8CAAA,EAAgD,OAAA;AAAA,EAChD,4CAAA,EAA8C,OAAA;AAAA,EAC9C,6CAAA,EAA+C,OAAA;AAAA,EAC/C,mCAAA,EAAqC,OAAA;AAAA,EACrC,8BAAA,EAAgC,OAAA;AAAA,EAChC,6BAAA,EAA+B,OAAA;AAAA,EAC/B,yCAAA,EAA2C,OAAA;AAAA,EAC3C,qCAAA,EAAuC,OAAA;AAAA,EACvC,4CAAA,EAA8C,OAAA;AAAA,EAC9C,yCAAA,EAA2C,OAAA;AAAA,EAC3C,2CAAA,EAA6C,OAAA;AAAA,EAC7C,yCAAA,EAA2C,OAAA;AAAA,EAC3C,oDAAA,EAAsD;AACxD,CAAA;AAKA,MAAM,iBAAA,GAAoB;AAAA,EACxB,IAAA,EAAM,2BAAA;AAAA,EACN,OAAA,EAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOP,IAAI,eAAA,GAAgC;AAClC,MAAA,OAAO,uBAAA;AAAA,IACT;AAAA,GACF;AAAA,EACA,KAAA,EAAO;AACT,CAAA;AAEO,MAAM,uBAAA,GAA0B;AAAA,EACrC,IAAA,EAAM;AAAA,IACJ,IAAA,EAAM;AAAA,GACR;AAAA,EACA,KAAA,EAAO,gBAAA;AAAA,EACP,OAAA,EAAS;AAAA;AAAA,IAEP,WAAA,EAAa;AAAA;AAEjB;;;;"}
|
|
@@ -38,7 +38,7 @@ const buildCalleeResolver = (importDecl) => {
|
|
|
38
38
|
isNamespace: false
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
|
-
if (callee.type === AST_NODE_TYPES.MemberExpression && !callee.computed && callee.object.type === AST_NODE_TYPES.Identifier &&
|
|
41
|
+
if (namespaceName !== void 0 && callee.type === AST_NODE_TYPES.MemberExpression && !callee.computed && callee.object.type === AST_NODE_TYPES.Identifier && callee.object.name === namespaceName && callee.property.type === AST_NODE_TYPES.Identifier) {
|
|
42
42
|
return {
|
|
43
43
|
canonicalName: callee.property.name,
|
|
44
44
|
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import-utils.mjs","sources":["../../src/rules/import-utils.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { type DeepReadonly } from 'ts-type-forge';\nimport { TS_DATA_FORGE_MODULE } from './constants.mjs';\n\n/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */\n\nexport const getTsDataForgeImport = (\n program: TSESTree.Program,\n): TSESTree.ImportDeclaration | undefined =>\n program.body.find(\n (node): node is TSESTree.ImportDeclaration =>\n node.type === AST_NODE_TYPES.ImportDeclaration &&\n node.source.value === TS_DATA_FORGE_MODULE,\n );\n\nexport const getNamedImports = (\n node: DeepReadonly<TSESTree.ImportDeclaration> | undefined,\n): readonly string[] => {\n if (node === undefined) return [];\n\n return node.specifiers.flatMap((specifier) =>\n specifier.type === AST_NODE_TYPES.ImportSpecifier\n ? (() => {\n const importedName =\n specifier.imported.type === AST_NODE_TYPES.Identifier\n ? specifier.imported.name\n : specifier.imported.value;\n\n return typeof importedName === 'string' ? [importedName] : [];\n })()\n : [],\n );\n};\n\n/**\n * Returns the in-scope local name a given canonical export is imported as\n * (handling aliases such as `import { isX as y }` → `'y'`), or `undefined` when\n * it is not imported. Used so autofixes reference the binding that actually\n * exists rather than the canonical name.\n */\nexport const getImportedLocalName = (\n node: DeepReadonly<TSESTree.ImportDeclaration> | undefined,\n importedName: string,\n): string | undefined =>\n node?.specifiers.find(\n (specifier) =>\n specifier.type === AST_NODE_TYPES.ImportSpecifier &&\n (specifier.imported.type === AST_NODE_TYPES.Identifier\n ? specifier.imported.name\n : specifier.imported.value) === importedName,\n )?.local.name;\n\ntype ResolvedCallee = Readonly<{\n /** The canonical (imported) ts-data-forge name, regardless of local alias. */\n canonicalName: string;\n /** The identifier node to rewrite when replacing the callee name. */\n propertyNode: TSESTree.Node;\n /** Whether the call was made through a namespace import (`tf.isX(...)`). */\n isNamespace: boolean;\n}>;\n\n/**\n * Builds a resolver that maps a call expression's callee to the canonical\n * ts-data-forge function it refers to. Handles named imports (including aliases\n * such as `import { isX as y }`) and namespace imports (`import * as tf` →\n * `tf.isX(...)`). Returns `undefined` for any callee that does not reference the\n * given ts-data-forge import.\n */\nexport const buildCalleeResolver = (\n importDecl: TSESTree.ImportDeclaration | undefined,\n): ((\n callee: DeepReadonly<TSESTree.Expression>,\n) => ResolvedCallee | undefined) => {\n const specifiers = importDecl?.specifiers ?? [];\n\n const namespaceName = specifiers.find(\n (specifier) => specifier.type === AST_NODE_TYPES.ImportNamespaceSpecifier,\n )?.local.name;\n\n const localToCanonical = new Map<string, string>(\n specifiers.flatMap((specifier) => {\n if (specifier.type !== AST_NODE_TYPES.ImportSpecifier) return [];\n\n const importedName =\n specifier.imported.type === AST_NODE_TYPES.Identifier\n ? specifier.imported.name\n : specifier.imported.value;\n\n return typeof importedName === 'string'\n ? [[specifier.local.name, importedName] as const]\n : [];\n }),\n );\n\n return (callee) => {\n if (callee.type === AST_NODE_TYPES.Identifier) {\n const canonicalName = localToCanonical.get(callee.name);\n\n return canonicalName === undefined\n ? undefined\n : {\n canonicalName,\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n propertyNode: callee as TSESTree.Node,\n isNamespace: false,\n };\n }\n\n if (\n callee.type === AST_NODE_TYPES.MemberExpression &&\n !callee.computed &&\n callee.object.type === AST_NODE_TYPES.Identifier &&\n
|
|
1
|
+
{"version":3,"file":"import-utils.mjs","sources":["../../src/rules/import-utils.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { type DeepReadonly } from 'ts-type-forge';\nimport { TS_DATA_FORGE_MODULE } from './constants.mjs';\n\n/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */\n\nexport const getTsDataForgeImport = (\n program: TSESTree.Program,\n): TSESTree.ImportDeclaration | undefined =>\n program.body.find(\n (node): node is TSESTree.ImportDeclaration =>\n node.type === AST_NODE_TYPES.ImportDeclaration &&\n node.source.value === TS_DATA_FORGE_MODULE,\n );\n\nexport const getNamedImports = (\n node: DeepReadonly<TSESTree.ImportDeclaration> | undefined,\n): readonly string[] => {\n if (node === undefined) return [];\n\n return node.specifiers.flatMap((specifier) =>\n specifier.type === AST_NODE_TYPES.ImportSpecifier\n ? (() => {\n const importedName =\n specifier.imported.type === AST_NODE_TYPES.Identifier\n ? specifier.imported.name\n : specifier.imported.value;\n\n return typeof importedName === 'string' ? [importedName] : [];\n })()\n : [],\n );\n};\n\n/**\n * Returns the in-scope local name a given canonical export is imported as\n * (handling aliases such as `import { isX as y }` → `'y'`), or `undefined` when\n * it is not imported. Used so autofixes reference the binding that actually\n * exists rather than the canonical name.\n */\nexport const getImportedLocalName = (\n node: DeepReadonly<TSESTree.ImportDeclaration> | undefined,\n importedName: string,\n): string | undefined =>\n node?.specifiers.find(\n (specifier) =>\n specifier.type === AST_NODE_TYPES.ImportSpecifier &&\n (specifier.imported.type === AST_NODE_TYPES.Identifier\n ? specifier.imported.name\n : specifier.imported.value) === importedName,\n )?.local.name;\n\ntype ResolvedCallee = Readonly<{\n /** The canonical (imported) ts-data-forge name, regardless of local alias. */\n canonicalName: string;\n /** The identifier node to rewrite when replacing the callee name. */\n propertyNode: TSESTree.Node;\n /** Whether the call was made through a namespace import (`tf.isX(...)`). */\n isNamespace: boolean;\n}>;\n\n/**\n * Builds a resolver that maps a call expression's callee to the canonical\n * ts-data-forge function it refers to. Handles named imports (including aliases\n * such as `import { isX as y }`) and namespace imports (`import * as tf` →\n * `tf.isX(...)`). Returns `undefined` for any callee that does not reference the\n * given ts-data-forge import.\n */\nexport const buildCalleeResolver = (\n importDecl: TSESTree.ImportDeclaration | undefined,\n): ((\n callee: DeepReadonly<TSESTree.Expression>,\n) => ResolvedCallee | undefined) => {\n const specifiers = importDecl?.specifiers ?? [];\n\n const namespaceName = specifiers.find(\n (specifier) => specifier.type === AST_NODE_TYPES.ImportNamespaceSpecifier,\n )?.local.name;\n\n const localToCanonical = new Map<string, string>(\n specifiers.flatMap((specifier) => {\n if (specifier.type !== AST_NODE_TYPES.ImportSpecifier) return [];\n\n const importedName =\n specifier.imported.type === AST_NODE_TYPES.Identifier\n ? specifier.imported.name\n : specifier.imported.value;\n\n return typeof importedName === 'string'\n ? [[specifier.local.name, importedName] as const]\n : [];\n }),\n );\n\n return (callee) => {\n if (callee.type === AST_NODE_TYPES.Identifier) {\n const canonicalName = localToCanonical.get(callee.name);\n\n return canonicalName === undefined\n ? undefined\n : {\n canonicalName,\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n propertyNode: callee as TSESTree.Node,\n isNamespace: false,\n };\n }\n\n if (\n namespaceName !== undefined &&\n callee.type === AST_NODE_TYPES.MemberExpression &&\n !callee.computed &&\n callee.object.type === AST_NODE_TYPES.Identifier &&\n callee.object.name === namespaceName &&\n callee.property.type === AST_NODE_TYPES.Identifier\n ) {\n return {\n canonicalName: callee.property.name,\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n propertyNode: callee.property as TSESTree.Node,\n isNamespace: true,\n };\n }\n\n return undefined;\n };\n};\n\nexport const buildImportFixes = (\n fixer: TSESLint.RuleFixer,\n program: TSESTree.Program,\n _tsDataForgeImport: TSESTree.ImportDeclaration | undefined,\n requiredNames: readonly string[],\n): readonly TSESLint.RuleFix[] => {\n const specifierText = requiredNames.join(', ');\n\n const importStatement =\n `import { ${specifierText} } from '${TS_DATA_FORGE_MODULE}';` as const;\n\n const newLine = '\\n';\n\n const insertionText = `${importStatement}${newLine}` as const;\n\n // Always insert at the beginning of the file\n // (organize-imports will handle merging/deduplication)\n return [fixer.insertTextBefore(program, insertionText)];\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-unnecessary-type-guard.mjs","sources":["../../src/rules/no-unnecessary-type-guard.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n ESLintUtils,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { type DeepReadonly } from 'ts-type-forge';\nimport * as ts from 'typescript';\nimport {\n buildCalleeResolver,\n buildImportFixes,\n getImportedLocalName,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\ntype Options = readonly [\n Readonly<{\n /** Names of ts-data-forge guard functions to skip checking. */\n ignore?: readonly string[];\n }>?,\n];\n\ntype MessageIds = 'alwaysTrue' | 'alwaysFalse' | 'replaceTypeGuard';\n\n/**\n * The primitive categories a (non-`null`/`undefined`) union member can fall\n * into. `other` covers anything the rule does not reason about (objects,\n * functions, `void`, …).\n */\ntype AtomKey =\n 'undefined' | 'null' | 'boolean' | 'number' | 'string' | 'bigint' | 'symbol';\n\ntype Atom = AtomKey | 'other';\n\ntype GuardSpec = DeepReadonly<\n | {\n kind: 'narrowTo';\n atoms: AtomKey[];\n replacements?: Record<string, string>;\n }\n | {\n kind: 'excludeFrom';\n atoms: AtomKey[];\n replacements?: Record<string, string>;\n }\n | { kind: 'nonEmptyString' }\n | { kind: 'record' }\n>;\n\n/**\n * Internal marker property attached to every `ts-type-forge` branded type. Its\n * presence (together with the `MinLength` brand key) lets us recognize a\n * `NonEmptyString` without holding a reference to the type itself.\n */\nconst BRAND_MARKER_PROPERTY =\n 'TSTypeForgeInternals--edd2f9ce-7ca5-45b0-9d1a-bd61b9b5d9c3';\n\n/**\n * Brand key carried by `MinLengthString<N>` — and therefore by\n * `NonEmptyString = MinLengthString<1>`. Its value is `MinLengthTuple<N, 0>`, a\n * tuple of `0`s with at least `N` elements, so a required first element means\n * `N >= 1` (the string cannot be empty).\n */\nconst MIN_LENGTH_BRAND_KEY = 'MinLength';\n\nconst NULLISH_REPLACEMENTS_EXCLUDE: Readonly<Record<string, string>> = {\n undefined: 'isNotUndefined',\n null: 'isNotNull',\n} as const;\n\nconst NULLISH_REPLACEMENTS_NARROW: Readonly<Record<string, string>> = {\n undefined: 'isUndefined',\n null: 'isNull',\n} as const;\n\nconst GUARD_SPECS: DeepReadonly<Record<string, GuardSpec>> = {\n isUndefined: { kind: 'narrowTo', atoms: ['undefined'] },\n isNotUndefined: { kind: 'excludeFrom', atoms: ['undefined'] },\n isNull: { kind: 'narrowTo', atoms: ['null'] },\n isNotNull: { kind: 'excludeFrom', atoms: ['null'] },\n isBoolean: { kind: 'narrowTo', atoms: ['boolean'] },\n isNotBoolean: { kind: 'excludeFrom', atoms: ['boolean'] },\n isNumber: { kind: 'narrowTo', atoms: ['number'] },\n isNotNumber: { kind: 'excludeFrom', atoms: ['number'] },\n isString: { kind: 'narrowTo', atoms: ['string'] },\n isNotString: { kind: 'excludeFrom', atoms: ['string'] },\n isBigint: { kind: 'narrowTo', atoms: ['bigint'] },\n isNotBigint: { kind: 'excludeFrom', atoms: ['bigint'] },\n isSymbol: { kind: 'narrowTo', atoms: ['symbol'] },\n isNotSymbol: { kind: 'excludeFrom', atoms: ['symbol'] },\n isNullish: {\n kind: 'narrowTo',\n atoms: ['null', 'undefined'],\n replacements: NULLISH_REPLACEMENTS_NARROW,\n },\n isNonNullish: {\n kind: 'excludeFrom',\n atoms: ['null', 'undefined'],\n replacements: NULLISH_REPLACEMENTS_EXCLUDE,\n },\n isNonEmptyString: { kind: 'nonEmptyString' },\n isRecord: { kind: 'record' },\n} as const;\n\nconst DEFERRED_OR_OPAQUE_FLAGS =\n ts.TypeFlags.Any |\n ts.TypeFlags.Unknown |\n ts.TypeFlags.Never |\n ts.TypeFlags.TypeParameter |\n ts.TypeFlags.IndexedAccess |\n ts.TypeFlags.Conditional |\n ts.TypeFlags.Substitution;\n\nexport const noUnnecessaryTypeGuard: TSESLint.RuleModule<MessageIds, Options> =\n {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Detect ts-data-forge type guard calls that perform no narrowing (the argument type already satisfies, or can never satisfy, the guard).',\n },\n fixable: 'code',\n schema: [\n {\n type: 'object',\n properties: {\n ignore: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'Names of ts-data-forge guard functions to skip checking.',\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n alwaysTrue:\n 'Unnecessary `{{name}}` call: the argument type guarantees it always returns `true`. Replace the call with `true`.',\n alwaysFalse:\n 'Unnecessary `{{name}}` call: the argument type guarantees it always returns `false`. Replace the call with `false`.',\n replaceTypeGuard:\n 'Unnecessary `{{from}}` call: the argument can only be narrowed by `{{to}}`. Use `{{to}}` instead.',\n },\n },\n\n create: (context) => {\n const options = context.options[0];\n\n const parserServices = ESLintUtils.getParserServices(context);\n\n const compilerOptions = parserServices.program.getCompilerOptions();\n\n // Without `strictNullChecks`, `null` / `undefined` are erased from the type\n // system, so reasoning about nullish narrowing is unsound. Disable entirely.\n const strictNullChecks =\n compilerOptions.strictNullChecks ?? compilerOptions.strict ?? false;\n\n if (!strictNullChecks) return {};\n\n const checker = parserServices.program.getTypeChecker();\n\n const sourceCode = context.sourceCode;\n\n const program = sourceCode.ast;\n\n const ignored = new Set(options?.ignore);\n\n const tsDataForgeImport = getTsDataForgeImport(program);\n\n const resolveGuard = buildCalleeResolver(tsDataForgeImport);\n\n const buildReplaceFix =\n (\n isNamespace: boolean,\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n propertyNode: TSESTree.Node,\n replacement: string,\n ): TSESLint.ReportFixFunction =>\n (fixer) => {\n // A namespace member access (`tf.isX`) exposes every export, so the\n // canonical name is always in scope.\n if (isNamespace) {\n return [fixer.replaceText(propertyNode, replacement)];\n }\n\n // If the replacement guard is already imported (possibly under an\n // alias), rename to its in-scope local name. Otherwise add a new\n // import and rename to the canonical name.\n const localName = getImportedLocalName(\n tsDataForgeImport,\n replacement,\n );\n\n if (localName !== undefined) {\n return [fixer.replaceText(propertyNode, localName)];\n }\n\n return [\n ...buildImportFixes(fixer, program, tsDataForgeImport, [\n replacement,\n ]),\n fixer.replaceText(propertyNode, replacement),\n ];\n };\n\n const reportConstant = (\n node: DeepReadonly<TSESTree.CallExpression>,\n name: string,\n value: boolean,\n argument: DeepReadonly<TSESTree.Expression>,\n ): void => {\n // Replacing the call with a literal drops the argument expression, so only\n // autofix when evaluating it cannot have observable side effects.\n const canFix = isSideEffectFreeArg(argument);\n\n context.report({\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n node: node as TSESTree.Node,\n messageId: value ? 'alwaysTrue' : 'alwaysFalse',\n data: { name },\n fix: canFix\n ? (fixer) =>\n fixer.replaceText(\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n node as TSESTree.Node,\n value ? 'true' : 'false',\n )\n : undefined,\n });\n };\n\n const reportReplace = (\n node: DeepReadonly<TSESTree.CallExpression>,\n from: string,\n to: string,\n isNamespace: boolean,\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n propertyNode: TSESTree.Node,\n ): void => {\n context.report({\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n node: node as TSESTree.Node,\n messageId: 'replaceTypeGuard',\n data: { from, to },\n fix: buildReplaceFix(isNamespace, propertyNode, to),\n });\n };\n\n return {\n CallExpression: (node) => {\n if (node.arguments.length !== 1) return;\n\n const argument = node.arguments[0];\n\n if (\n argument === undefined ||\n argument.type === AST_NODE_TYPES.SpreadElement\n ) {\n return;\n }\n\n const resolved = resolveGuard(node.callee);\n\n if (resolved === undefined) return;\n\n const { canonicalName, propertyNode, isNamespace } = resolved;\n\n if (ignored.has(canonicalName)) return;\n\n const spec = GUARD_SPECS[canonicalName];\n\n if (spec === undefined) return;\n\n const argTsNode = parserServices.esTreeNodeToTSNodeMap.get(argument);\n\n const argType = checker.getTypeAtLocation(argTsNode);\n\n const parts = collectUnionParts(argType);\n\n if (parts === undefined) return; // opaque / generic → bail conservatively\n\n const inputAtoms = new Set(parts.map(classifyAtom));\n\n const hasMemberOutsideTarget = (\n atoms: readonly AtomKey[],\n ): boolean => {\n const targetSet = new Set<Atom>(atoms);\n\n return parts.some((p) => !targetSet.has(classifyAtom(p)));\n };\n\n switch (spec.kind) {\n case 'excludeFrom': {\n const removable = spec.atoms.filter((a) => inputAtoms.has(a));\n\n if (removable.length === 0) {\n // Nothing to exclude → the guard always holds.\n reportConstant(node, canonicalName, true, argument);\n } else if (!hasMemberOutsideTarget(spec.atoms)) {\n // Every member is excluded → the guard never holds.\n reportConstant(node, canonicalName, false, argument);\n } else if (removable.length < spec.atoms.length) {\n const replacement = spec.replacements?.[joinAtoms(removable)];\n\n if (replacement !== undefined) {\n reportReplace(\n node,\n canonicalName,\n replacement,\n isNamespace,\n propertyNode,\n );\n }\n }\n\n break;\n }\n\n case 'narrowTo': {\n const present = spec.atoms.filter((a) => inputAtoms.has(a));\n\n if (present.length === 0) {\n // No member is in the target set → the guard never holds.\n reportConstant(node, canonicalName, false, argument);\n } else if (!hasMemberOutsideTarget(spec.atoms)) {\n // Input ⊆ target → the guard always holds and narrows nothing.\n reportConstant(node, canonicalName, true, argument);\n } else if (present.length < spec.atoms.length) {\n const replacement = spec.replacements?.[joinAtoms(present)];\n\n if (replacement !== undefined) {\n reportReplace(\n node,\n canonicalName,\n replacement,\n isNamespace,\n propertyNode,\n );\n }\n }\n\n break;\n }\n\n case 'record': {\n if (parts.every((p) => isGuaranteedRecord(p, checker))) {\n // Every member already satisfies `UnknownRecord`.\n reportConstant(node, canonicalName, true, argument);\n } else if (\n parts.every((p) => isGuaranteedNotRecord(p, checker))\n ) {\n // No member can ever be a non-null, non-array object.\n reportConstant(node, canonicalName, false, argument);\n }\n\n break;\n }\n\n case 'nonEmptyString': {\n const hasNullish =\n inputAtoms.has('undefined') || inputAtoms.has('null');\n\n const nonNullishParts = parts.filter((p) => {\n const atom = classifyAtom(p);\n\n return atom !== 'undefined' && atom !== 'null';\n });\n\n if (!nonNullishParts.some(couldBeNonEmptyString)) {\n // No member can be a non-empty string → the guard never holds.\n reportConstant(node, canonicalName, false, argument);\n\n break;\n }\n\n if (\n nonNullishParts.some(\n (p) => !isGuaranteedNonEmptyString(p, checker),\n )\n ) {\n break; // still does real string/empty work\n }\n\n if (hasNullish) {\n reportReplace(\n node,\n canonicalName,\n 'isNonNullish',\n isNamespace,\n propertyNode,\n );\n } else {\n reportConstant(node, canonicalName, true, argument);\n }\n\n break;\n }\n }\n },\n };\n },\n defaultOptions: [{}],\n } as const;\n\n/**\n * Decomposes a type into its union members. Returns `undefined` when any member\n * is opaque (`any` / `unknown` / `never`) or deferred (type parameter, indexed\n * access, …), signalling that the rule should not draw conclusions.\n */\n// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\nconst collectUnionParts = (type: ts.Type): readonly ts.Type[] | undefined => {\n const parts = type.isUnion() ? type.types : ([type] as const);\n\n for (const part of parts) {\n if ((part.flags & DEFERRED_OR_OPAQUE_FLAGS) !== 0) return undefined;\n }\n\n return parts;\n};\n\n/**\n * Whether `isRecord` is guaranteed to accept this (non-union) type.\n *\n * `isRecord` narrows to `UnknownRecord` (`ReadonlyRecord<string, unknown>`), so\n * a string index signature is exactly what it asks for and its value type is\n * `unknown`, which anything satisfies. Arrays and callables are excluded even\n * when they carry one, because `isRecord` rejects them at runtime\n * (`Array.isArray` is true; `typeof fn === 'object'` is false).\n *\n * Deliberately conservative: an object type without an index signature of its\n * own is reported as neither guaranteed-accepted nor guaranteed-rejected, even\n * where TypeScript would accept it through an implicit index signature.\n */\nconst isGuaranteedRecord = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n type: ts.Type,\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n checker: ts.TypeChecker,\n): boolean =>\n checker.getIndexInfoOfType(type, ts.IndexKind.String) !== undefined &&\n !isCallableType(type, checker) &&\n !checker.isArrayType(type) &&\n !checker.isTupleType(type);\n\n/**\n * Whether `isRecord` is guaranteed to reject this (non-union) type: every\n * primitive (`null` included), every array or tuple, and every callable fails\n * its `typeof x === 'object' && x !== null && !Array.isArray(x)` check.\n */\nconst isGuaranteedNotRecord = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n type: ts.Type,\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n checker: ts.TypeChecker,\n): boolean =>\n classifyAtom(type) !== 'other' ||\n checker.isArrayType(type) ||\n checker.isTupleType(type) ||\n isCallableType(type, checker);\n\nconst isCallableType = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n type: ts.Type,\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n checker: ts.TypeChecker,\n): boolean =>\n checker.getSignaturesOfType(type, ts.SignatureKind.Call).length > 0 ||\n checker.getSignaturesOfType(type, ts.SignatureKind.Construct).length > 0;\n\n/** Classifies a single (non-union) type into a primitive {@link Atom}. */\n// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\nconst classifyAtom = (type: ts.Type): Atom => {\n const { flags } = type;\n\n if ((flags & ts.TypeFlags.Undefined) !== 0) return 'undefined';\n\n if ((flags & ts.TypeFlags.Null) !== 0) return 'null';\n\n if ((flags & ts.TypeFlags.BooleanLike) !== 0) return 'boolean';\n\n if ((flags & ts.TypeFlags.NumberLike) !== 0) return 'number';\n\n if ((flags & ts.TypeFlags.StringLike) !== 0) return 'string';\n\n if ((flags & ts.TypeFlags.BigIntLike) !== 0) return 'bigint';\n\n if ((flags & ts.TypeFlags.ESSymbolLike) !== 0) return 'symbol';\n\n // Branded primitives (e.g. `NonEmptyString = string & {...}`) are\n // intersections; classify them by their underlying primitive constituent.\n if (type.isIntersection()) {\n for (const constituent of type.types) {\n const atom = classifyAtom(constituent);\n\n if (atom !== 'other') return atom;\n }\n }\n\n return 'other';\n};\n\nconst ATOM_ORDER: readonly AtomKey[] = [\n 'undefined',\n 'null',\n 'boolean',\n 'number',\n 'string',\n 'bigint',\n 'symbol',\n] as const;\n\n/** A stable key for a set of atoms, used to look up replacement guards. */\nconst joinAtoms = (atoms: readonly AtomKey[]): string =>\n ATOM_ORDER.filter((atom) => atoms.includes(atom)).join('|');\n\n/**\n * Returns `true` when the type is guaranteed to be a non-empty string: either a\n * non-empty string literal or a value branded as `NonEmptyString`\n * (`MinLengthString<N>` with `N >= 1`).\n */\nconst isGuaranteedNonEmptyString = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n type: ts.Type,\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n checker: ts.TypeChecker,\n): boolean => {\n if (type.isStringLiteral()) return type.value !== '';\n\n if (type.getProperty(BRAND_MARKER_PROPERTY) === undefined) return false;\n\n const minLengthSymbol = type.getProperty(MIN_LENGTH_BRAND_KEY);\n\n if (minLengthSymbol === undefined) return false;\n\n // The `MinLength` brand value is `MinLengthTuple<N, 0>`; it has a required\n // first element (`'0'`) exactly when `N >= 1`, i.e. the string is non-empty.\n // `MinLengthString<0>` brands with a plain array and has no such element.\n return (\n checker.getTypeOfSymbol(minLengthSymbol).getProperty('0') !== undefined\n );\n};\n\n/**\n * Returns `true` when the type can possibly be a non-empty string (a general\n * `string`, a branded string, or a non-empty string literal). The empty string\n * literal and non-string types cannot.\n */\n// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\nconst couldBeNonEmptyString = (type: ts.Type): boolean => {\n if (type.isStringLiteral()) return type.value !== '';\n\n return classifyAtom(type) === 'string';\n};\n\n/**\n * Returns `true` when evaluating the expression cannot have observable side\n * effects, so replacing the surrounding call with a literal is safe. Limited to\n * simple references (identifiers, `this`, literals, and member-access chains).\n */\n\nconst isSideEffectFreeArg = (node: DeepReadonly<TSESTree.Node>): boolean => {\n // `node.type` is one of 130+ AST kinds; only a handful are relevant here.\n // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check\n switch (node.type) {\n case AST_NODE_TYPES.Identifier:\n case AST_NODE_TYPES.ThisExpression:\n case AST_NODE_TYPES.Literal:\n return true;\n\n case AST_NODE_TYPES.MemberExpression:\n return (\n isSideEffectFreeArg(node.object) &&\n (!node.computed || isSideEffectFreeArg(node.property))\n );\n\n default:\n return false;\n }\n};\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"no-unnecessary-type-guard.mjs","sources":["../../src/rules/no-unnecessary-type-guard.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n ESLintUtils,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { type DeepReadonly, type ReadonlyRecord } from 'ts-type-forge';\nimport * as ts from 'typescript';\nimport {\n buildCalleeResolver,\n buildImportFixes,\n getImportedLocalName,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\ntype Options = readonly [\n Readonly<{\n /** Names of ts-data-forge guard functions to skip checking. */\n ignore?: readonly string[];\n }>?,\n];\n\ntype MessageIds = 'alwaysTrue' | 'alwaysFalse' | 'replaceTypeGuard';\n\n/**\n * The primitive categories a (non-`null`/`undefined`) union member can fall\n * into. `other` covers anything the rule does not reason about (objects,\n * functions, `void`, …).\n */\ntype AtomKey =\n 'undefined' | 'null' | 'boolean' | 'number' | 'string' | 'bigint' | 'symbol';\n\ntype Atom = AtomKey | 'other';\n\ntype GuardSpec = DeepReadonly<\n | {\n kind: 'narrowTo';\n atoms: AtomKey[];\n replacements?: ReadonlyRecord<string, string>;\n }\n | {\n kind: 'excludeFrom';\n atoms: AtomKey[];\n replacements?: ReadonlyRecord<string, string>;\n }\n | { kind: 'nonEmptyString' }\n | { kind: 'record' }\n>;\n\n/**\n * Internal marker property attached to every `ts-type-forge` branded type. Its\n * presence (together with the `MinLength` brand key) lets us recognize a\n * `NonEmptyString` without holding a reference to the type itself.\n */\nconst BRAND_MARKER_PROPERTY =\n 'TSTypeForgeInternals--edd2f9ce-7ca5-45b0-9d1a-bd61b9b5d9c3';\n\n/**\n * Brand key carried by `MinLengthString<N>` — and therefore by\n * `NonEmptyString = MinLengthString<1>`. Its value is `MinLengthTuple<N, 0>`, a\n * tuple of `0`s with at least `N` elements, so a required first element means\n * `N >= 1` (the string cannot be empty).\n */\nconst MIN_LENGTH_BRAND_KEY = 'MinLength';\n\nconst NULLISH_REPLACEMENTS_EXCLUDE: ReadonlyRecord<string, string> = {\n undefined: 'isNotUndefined',\n null: 'isNotNull',\n} as const;\n\nconst NULLISH_REPLACEMENTS_NARROW: ReadonlyRecord<string, string> = {\n undefined: 'isUndefined',\n null: 'isNull',\n} as const;\n\nconst GUARD_SPECS: DeepReadonly<ReadonlyRecord<string, GuardSpec>> = {\n isUndefined: { kind: 'narrowTo', atoms: ['undefined'] },\n isNotUndefined: { kind: 'excludeFrom', atoms: ['undefined'] },\n isNull: { kind: 'narrowTo', atoms: ['null'] },\n isNotNull: { kind: 'excludeFrom', atoms: ['null'] },\n isBoolean: { kind: 'narrowTo', atoms: ['boolean'] },\n isNotBoolean: { kind: 'excludeFrom', atoms: ['boolean'] },\n isNumber: { kind: 'narrowTo', atoms: ['number'] },\n isNotNumber: { kind: 'excludeFrom', atoms: ['number'] },\n isString: { kind: 'narrowTo', atoms: ['string'] },\n isNotString: { kind: 'excludeFrom', atoms: ['string'] },\n isBigint: { kind: 'narrowTo', atoms: ['bigint'] },\n isNotBigint: { kind: 'excludeFrom', atoms: ['bigint'] },\n isSymbol: { kind: 'narrowTo', atoms: ['symbol'] },\n isNotSymbol: { kind: 'excludeFrom', atoms: ['symbol'] },\n isNullish: {\n kind: 'narrowTo',\n atoms: ['null', 'undefined'],\n replacements: NULLISH_REPLACEMENTS_NARROW,\n },\n isNonNullish: {\n kind: 'excludeFrom',\n atoms: ['null', 'undefined'],\n replacements: NULLISH_REPLACEMENTS_EXCLUDE,\n },\n isNonEmptyString: { kind: 'nonEmptyString' },\n isRecord: { kind: 'record' },\n} as const;\n\nconst DEFERRED_OR_OPAQUE_FLAGS =\n ts.TypeFlags.Any |\n ts.TypeFlags.Unknown |\n ts.TypeFlags.Never |\n ts.TypeFlags.TypeParameter |\n ts.TypeFlags.IndexedAccess |\n ts.TypeFlags.Conditional |\n ts.TypeFlags.Substitution;\n\nexport const noUnnecessaryTypeGuard: TSESLint.RuleModule<MessageIds, Options> =\n {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Detect ts-data-forge type guard calls that perform no narrowing (the argument type already satisfies, or can never satisfy, the guard).',\n },\n fixable: 'code',\n schema: [\n {\n type: 'object',\n properties: {\n ignore: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'Names of ts-data-forge guard functions to skip checking.',\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n alwaysTrue:\n 'Unnecessary `{{name}}` call: the argument type guarantees it always returns `true`. Replace the call with `true`.',\n alwaysFalse:\n 'Unnecessary `{{name}}` call: the argument type guarantees it always returns `false`. Replace the call with `false`.',\n replaceTypeGuard:\n 'Unnecessary `{{from}}` call: the argument can only be narrowed by `{{to}}`. Use `{{to}}` instead.',\n },\n },\n\n create: (context) => {\n const options = context.options[0];\n\n const parserServices = ESLintUtils.getParserServices(context);\n\n const compilerOptions = parserServices.program.getCompilerOptions();\n\n // Without `strictNullChecks`, `null` / `undefined` are erased from the type\n // system, so reasoning about nullish narrowing is unsound. Disable entirely.\n const strictNullChecks =\n compilerOptions.strictNullChecks ?? compilerOptions.strict ?? false;\n\n if (!strictNullChecks) return {};\n\n const checker = parserServices.program.getTypeChecker();\n\n const sourceCode = context.sourceCode;\n\n const program = sourceCode.ast;\n\n const ignored = new Set(options?.ignore);\n\n const tsDataForgeImport = getTsDataForgeImport(program);\n\n const resolveGuard = buildCalleeResolver(tsDataForgeImport);\n\n const buildReplaceFix =\n (\n isNamespace: boolean,\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n propertyNode: TSESTree.Node,\n replacement: string,\n ): TSESLint.ReportFixFunction =>\n (fixer) => {\n // A namespace member access (`tf.isX`) exposes every export, so the\n // canonical name is always in scope.\n if (isNamespace) {\n return [fixer.replaceText(propertyNode, replacement)];\n }\n\n // If the replacement guard is already imported (possibly under an\n // alias), rename to its in-scope local name. Otherwise add a new\n // import and rename to the canonical name.\n const localName = getImportedLocalName(\n tsDataForgeImport,\n replacement,\n );\n\n if (localName !== undefined) {\n return [fixer.replaceText(propertyNode, localName)];\n }\n\n return [\n ...buildImportFixes(fixer, program, tsDataForgeImport, [\n replacement,\n ]),\n fixer.replaceText(propertyNode, replacement),\n ];\n };\n\n const reportConstant = (\n node: DeepReadonly<TSESTree.CallExpression>,\n name: string,\n value: boolean,\n argument: DeepReadonly<TSESTree.Expression>,\n ): void => {\n // Replacing the call with a literal drops the argument expression, so only\n // autofix when evaluating it cannot have observable side effects.\n const canFix = isSideEffectFreeArg(argument);\n\n context.report({\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n node: node as TSESTree.Node,\n messageId: value ? 'alwaysTrue' : 'alwaysFalse',\n data: { name },\n fix: canFix\n ? (fixer) =>\n fixer.replaceText(\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n node as TSESTree.Node,\n value ? 'true' : 'false',\n )\n : undefined,\n });\n };\n\n const reportReplace = (\n node: DeepReadonly<TSESTree.CallExpression>,\n from: string,\n to: string,\n isNamespace: boolean,\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n propertyNode: TSESTree.Node,\n ): void => {\n context.report({\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n node: node as TSESTree.Node,\n messageId: 'replaceTypeGuard',\n data: { from, to },\n fix: buildReplaceFix(isNamespace, propertyNode, to),\n });\n };\n\n return {\n CallExpression: (node) => {\n if (node.arguments.length !== 1) return;\n\n const argument = node.arguments[0];\n\n if (\n argument === undefined ||\n argument.type === AST_NODE_TYPES.SpreadElement\n ) {\n return;\n }\n\n const resolved = resolveGuard(node.callee);\n\n if (resolved === undefined) return;\n\n const { canonicalName, propertyNode, isNamespace } = resolved;\n\n if (ignored.has(canonicalName)) return;\n\n const spec = GUARD_SPECS[canonicalName];\n\n if (spec === undefined) return;\n\n const argTsNode = parserServices.esTreeNodeToTSNodeMap.get(argument);\n\n const argType = checker.getTypeAtLocation(argTsNode);\n\n const parts = collectUnionParts(argType);\n\n if (parts === undefined) return; // opaque / generic → bail conservatively\n\n const inputAtoms = new Set(parts.map(classifyAtom));\n\n const hasMemberOutsideTarget = (\n atoms: readonly AtomKey[],\n ): boolean => {\n const targetSet = new Set<Atom>(atoms);\n\n return parts.some((p) => !targetSet.has(classifyAtom(p)));\n };\n\n switch (spec.kind) {\n case 'excludeFrom': {\n const removable = spec.atoms.filter((a) => inputAtoms.has(a));\n\n if (removable.length === 0) {\n // Nothing to exclude → the guard always holds.\n reportConstant(node, canonicalName, true, argument);\n } else if (!hasMemberOutsideTarget(spec.atoms)) {\n // Every member is excluded → the guard never holds.\n reportConstant(node, canonicalName, false, argument);\n } else if (removable.length < spec.atoms.length) {\n const replacement = spec.replacements?.[joinAtoms(removable)];\n\n if (replacement !== undefined) {\n reportReplace(\n node,\n canonicalName,\n replacement,\n isNamespace,\n propertyNode,\n );\n }\n }\n\n break;\n }\n\n case 'narrowTo': {\n const present = spec.atoms.filter((a) => inputAtoms.has(a));\n\n if (present.length === 0) {\n // No member is in the target set → the guard never holds.\n reportConstant(node, canonicalName, false, argument);\n } else if (!hasMemberOutsideTarget(spec.atoms)) {\n // Input ⊆ target → the guard always holds and narrows nothing.\n reportConstant(node, canonicalName, true, argument);\n } else if (present.length < spec.atoms.length) {\n const replacement = spec.replacements?.[joinAtoms(present)];\n\n if (replacement !== undefined) {\n reportReplace(\n node,\n canonicalName,\n replacement,\n isNamespace,\n propertyNode,\n );\n }\n }\n\n break;\n }\n\n case 'record': {\n if (parts.every((p) => isGuaranteedRecord(p, checker))) {\n // Every member already satisfies `UnknownRecord`.\n reportConstant(node, canonicalName, true, argument);\n } else if (\n parts.every((p) => isGuaranteedNotRecord(p, checker))\n ) {\n // No member can ever be a non-null, non-array object.\n reportConstant(node, canonicalName, false, argument);\n }\n\n break;\n }\n\n case 'nonEmptyString': {\n const hasNullish =\n inputAtoms.has('undefined') || inputAtoms.has('null');\n\n const nonNullishParts = parts.filter((p) => {\n const atom = classifyAtom(p);\n\n return atom !== 'undefined' && atom !== 'null';\n });\n\n if (!nonNullishParts.some(couldBeNonEmptyString)) {\n // No member can be a non-empty string → the guard never holds.\n reportConstant(node, canonicalName, false, argument);\n\n break;\n }\n\n if (\n nonNullishParts.some(\n (p) => !isGuaranteedNonEmptyString(p, checker),\n )\n ) {\n break; // still does real string/empty work\n }\n\n if (hasNullish) {\n reportReplace(\n node,\n canonicalName,\n 'isNonNullish',\n isNamespace,\n propertyNode,\n );\n } else {\n reportConstant(node, canonicalName, true, argument);\n }\n\n break;\n }\n }\n },\n };\n },\n defaultOptions: [{}],\n } as const;\n\n/**\n * Decomposes a type into its union members. Returns `undefined` when any member\n * is opaque (`any` / `unknown` / `never`) or deferred (type parameter, indexed\n * access, …), signalling that the rule should not draw conclusions.\n */\n// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\nconst collectUnionParts = (type: ts.Type): readonly ts.Type[] | undefined => {\n const parts = type.isUnion() ? type.types : ([type] as const);\n\n for (const part of parts) {\n if ((part.flags & DEFERRED_OR_OPAQUE_FLAGS) !== 0) return undefined;\n }\n\n return parts;\n};\n\n/**\n * Whether `isRecord` is guaranteed to accept this (non-union) type.\n *\n * `isRecord` narrows to `UnknownRecord` (`ReadonlyRecord<string, unknown>`), so\n * a string index signature is exactly what it asks for and its value type is\n * `unknown`, which anything satisfies. Arrays and callables are excluded even\n * when they carry one, because `isRecord` rejects them at runtime\n * (`Array.isArray` is true; `typeof fn === 'object'` is false).\n *\n * Deliberately conservative: an object type without an index signature of its\n * own is reported as neither guaranteed-accepted nor guaranteed-rejected, even\n * where TypeScript would accept it through an implicit index signature.\n */\nconst isGuaranteedRecord = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n type: ts.Type,\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n checker: ts.TypeChecker,\n): boolean =>\n checker.getIndexInfoOfType(type, ts.IndexKind.String) !== undefined &&\n !isCallableType(type, checker) &&\n !checker.isArrayType(type) &&\n !checker.isTupleType(type);\n\n/**\n * Whether `isRecord` is guaranteed to reject this (non-union) type: every\n * primitive (`null` included), every array or tuple, and every callable fails\n * its `typeof x === 'object' && x !== null && !Array.isArray(x)` check.\n */\nconst isGuaranteedNotRecord = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n type: ts.Type,\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n checker: ts.TypeChecker,\n): boolean =>\n classifyAtom(type) !== 'other' ||\n checker.isArrayType(type) ||\n checker.isTupleType(type) ||\n isCallableType(type, checker);\n\nconst isCallableType = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n type: ts.Type,\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n checker: ts.TypeChecker,\n): boolean =>\n checker.getSignaturesOfType(type, ts.SignatureKind.Call).length > 0 ||\n checker.getSignaturesOfType(type, ts.SignatureKind.Construct).length > 0;\n\n/** Classifies a single (non-union) type into a primitive {@link Atom}. */\n// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\nconst classifyAtom = (type: ts.Type): Atom => {\n const { flags } = type;\n\n if ((flags & ts.TypeFlags.Undefined) !== 0) return 'undefined';\n\n if ((flags & ts.TypeFlags.Null) !== 0) return 'null';\n\n if ((flags & ts.TypeFlags.BooleanLike) !== 0) return 'boolean';\n\n if ((flags & ts.TypeFlags.NumberLike) !== 0) return 'number';\n\n if ((flags & ts.TypeFlags.StringLike) !== 0) return 'string';\n\n if ((flags & ts.TypeFlags.BigIntLike) !== 0) return 'bigint';\n\n if ((flags & ts.TypeFlags.ESSymbolLike) !== 0) return 'symbol';\n\n // Branded primitives (e.g. `NonEmptyString = string & {...}`) are\n // intersections; classify them by their underlying primitive constituent.\n if (type.isIntersection()) {\n for (const constituent of type.types) {\n const atom = classifyAtom(constituent);\n\n if (atom !== 'other') return atom;\n }\n }\n\n return 'other';\n};\n\nconst ATOM_ORDER: readonly AtomKey[] = [\n 'undefined',\n 'null',\n 'boolean',\n 'number',\n 'string',\n 'bigint',\n 'symbol',\n] as const;\n\n/** A stable key for a set of atoms, used to look up replacement guards. */\nconst joinAtoms = (atoms: readonly AtomKey[]): string =>\n ATOM_ORDER.filter((atom) => atoms.includes(atom)).join('|');\n\n/**\n * Returns `true` when the type is guaranteed to be a non-empty string: either a\n * non-empty string literal or a value branded as `NonEmptyString`\n * (`MinLengthString<N>` with `N >= 1`).\n */\nconst isGuaranteedNonEmptyString = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n type: ts.Type,\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n checker: ts.TypeChecker,\n): boolean => {\n if (type.isStringLiteral()) return type.value !== '';\n\n if (type.getProperty(BRAND_MARKER_PROPERTY) === undefined) return false;\n\n const minLengthSymbol = type.getProperty(MIN_LENGTH_BRAND_KEY);\n\n if (minLengthSymbol === undefined) return false;\n\n // The `MinLength` brand value is `MinLengthTuple<N, 0>`; it has a required\n // first element (`'0'`) exactly when `N >= 1`, i.e. the string is non-empty.\n // `MinLengthString<0>` brands with a plain array and has no such element.\n return (\n checker.getTypeOfSymbol(minLengthSymbol).getProperty('0') !== undefined\n );\n};\n\n/**\n * Returns `true` when the type can possibly be a non-empty string (a general\n * `string`, a branded string, or a non-empty string literal). The empty string\n * literal and non-string types cannot.\n */\n// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\nconst couldBeNonEmptyString = (type: ts.Type): boolean => {\n if (type.isStringLiteral()) return type.value !== '';\n\n return classifyAtom(type) === 'string';\n};\n\n/**\n * Returns `true` when evaluating the expression cannot have observable side\n * effects, so replacing the surrounding call with a literal is safe. Limited to\n * simple references (identifiers, `this`, literals, and member-access chains).\n */\n\nconst isSideEffectFreeArg = (node: DeepReadonly<TSESTree.Node>): boolean => {\n // `node.type` is one of 130+ AST kinds; only a handful are relevant here.\n // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check\n switch (node.type) {\n case AST_NODE_TYPES.Identifier:\n case AST_NODE_TYPES.ThisExpression:\n case AST_NODE_TYPES.Literal:\n return true;\n\n case AST_NODE_TYPES.MemberExpression:\n return (\n isSideEffectFreeArg(node.object) &&\n (!node.computed || isSideEffectFreeArg(node.property))\n );\n\n default:\n return false;\n }\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-canonical-array-slicing.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-canonical-array-slicing.mts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"prefer-canonical-array-slicing.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-canonical-array-slicing.mts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;AAWlC,KAAK,OAAO,GAAG,SAAS,EAAE,CAAC;AAE3B,KAAK,UAAU,GAAG,6BAA6B,CAAC;AA+IhD,eAAO,MAAM,2BAA2B,EAAE,QAAQ,CAAC,UAAU,CAC3D,UAAU,EACV,OAAO,CA2nBC,CAAC"}
|
|
@@ -246,7 +246,7 @@ const preferCanonicalArraySlicing = {
|
|
|
246
246
|
if (methodName === "skip" || methodName === "skipLast") {
|
|
247
247
|
const isArrObject = receiver.type === AST_NODE_TYPES.Identifier && receiver.name === arrLocalName || receiver.type === AST_NODE_TYPES.MemberExpression && !receiver.computed && receiver.object.type === AST_NODE_TYPES.Identifier && receiver.object.name === namespaceLocalName && receiver.property.type === AST_NODE_TYPES.Identifier && receiver.property.name === "Arr";
|
|
248
248
|
const [arrayArg, countArg] = node.arguments;
|
|
249
|
-
if (isArrObject &&
|
|
249
|
+
if (isArrObject && arrayArg !== void 0 && countArg !== void 0 && node.arguments.length === 2 && arrayArg.type !== AST_NODE_TYPES.SpreadElement && asNonNegativeIntegerLiteral(countArg) === 1) {
|
|
250
250
|
report(
|
|
251
251
|
node,
|
|
252
252
|
{ fn: methodName === "skip" ? "tail" : "butLast" },
|
|
@@ -258,7 +258,7 @@ const preferCanonicalArraySlicing = {
|
|
|
258
258
|
if (methodName === "concat" && receiver.type === AST_NODE_TYPES.ArrayExpression) {
|
|
259
259
|
const [element] = receiver.elements;
|
|
260
260
|
const [arg] = node.arguments;
|
|
261
|
-
if (
|
|
261
|
+
if (element !== null && element !== void 0 && arg !== void 0 && receiver.elements.length === 1 && element.type !== AST_NODE_TYPES.SpreadElement && node.arguments.length === 1 && arg.type !== AST_NODE_TYPES.SpreadElement && isArrayOrTupleType(getTypeOf(arg))) {
|
|
262
262
|
report(
|
|
263
263
|
node,
|
|
264
264
|
{ fn: "toUnshifted", itemText: toArgText(element) },
|
|
@@ -298,12 +298,12 @@ const preferCanonicalArraySlicing = {
|
|
|
298
298
|
}
|
|
299
299
|
case "concat": {
|
|
300
300
|
const [arg] = node.arguments;
|
|
301
|
-
if (node.arguments.length !== 1 || arg
|
|
301
|
+
if (arg === void 0 || node.arguments.length !== 1 || arg.type === AST_NODE_TYPES.SpreadElement) {
|
|
302
302
|
return;
|
|
303
303
|
}
|
|
304
304
|
if (arg.type === AST_NODE_TYPES.ArrayExpression) {
|
|
305
305
|
const [element] = arg.elements;
|
|
306
|
-
if (
|
|
306
|
+
if (element !== null && element !== void 0 && arg.elements.length === 1 && element.type !== AST_NODE_TYPES.SpreadElement) {
|
|
307
307
|
report(
|
|
308
308
|
node,
|
|
309
309
|
{ fn: "toPushed", itemText: toArgText(element) },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-canonical-array-slicing.mjs","sources":["../../src/rules/prefer-canonical-array-slicing.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n ESLintUtils,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport * as ts from 'typescript';\nimport {\n buildImportFixes,\n getImportedLocalName,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\n/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */\n\ntype Options = readonly [];\n\ntype MessageIds = 'preferCanonicalArraySlicing';\n\n/**\n * A canonical ts-data-forge `Arr` operation a detected pattern is rewritten to:\n *\n * - `tail` / `butLast` — drop the first / last element\n * - `skip N` / `take N` — drop / keep the first `N` elements\n * - `skipLast N` / `takeLast N` — drop / keep the last `N` elements\n * - `toUnshifted item` / `toPushed item` — prepend / append one element\n * (`toUnshifted` is emitted in its curried form `toUnshifted(item)(xs)` so\n * the new element keeps the leading position it had in `[item, ...xs]`)\n */\ntype CopyOp = Readonly<\n | { fn: 'butLast' | 'tail' }\n | { fn: 'skip' | 'skipLast' | 'take' | 'takeLast'; count: number }\n | { fn: 'toPushed' | 'toUnshifted'; itemText: string }\n>;\n\n/** `skip 1` / `skipLast 1` have dedicated single-element names. */\nconst normalizeCountedOp = (\n fn: 'skip' | 'skipLast' | 'take' | 'takeLast',\n count: number,\n): CopyOp | undefined =>\n count < 1\n ? undefined\n : fn === 'skip' && count === 1\n ? ({ fn: 'tail' } as const)\n : fn === 'skipLast' && count === 1\n ? ({ fn: 'butLast' } as const)\n : ({ fn, count } as const);\n\nconst asNonNegativeIntegerLiteral = (\n node: TSESTree.Node,\n): number | undefined =>\n node.type === AST_NODE_TYPES.Literal &&\n typeof node.value === 'number' &&\n Number.isInteger(node.value) &&\n node.value >= 0\n ? node.value\n : undefined;\n\n/** Matches `-N` (unary minus of a positive integer literal) and returns `N`. */\nconst asNegatedPositiveIntegerLiteral = (\n node: TSESTree.Node,\n): number | undefined => {\n if (\n node.type !== AST_NODE_TYPES.UnaryExpression ||\n node.operator !== '-' ||\n node.argument.type !== AST_NODE_TYPES.Literal\n ) {\n return undefined;\n }\n\n const n = asNonNegativeIntegerLiteral(node.argument);\n\n return n !== undefined && n >= 1 ? n : undefined;\n};\n\n/**\n * Whether re-reading the expression is free of side effects (a plain variable\n * or a non-computed property chain). Required for patterns that reference the\n * receiver twice, such as `xs.filter((_, i) => i < xs.length - 1)`.\n */\nconst isSimpleReference = (node: TSESTree.Node): boolean =>\n node.type === AST_NODE_TYPES.Identifier ||\n node.type === AST_NODE_TYPES.ThisExpression ||\n (node.type === AST_NODE_TYPES.MemberExpression &&\n !node.computed &&\n node.property.type === AST_NODE_TYPES.Identifier &&\n isSimpleReference(node.object));\n\nconst rootIdentifierName = (node: TSESTree.Node): string | undefined =>\n node.type === AST_NODE_TYPES.Identifier\n ? node.name\n : node.type === AST_NODE_TYPES.MemberExpression\n ? rootIdentifierName(node.object)\n : undefined;\n\ntype ComparisonOperator = '!==' | '<' | '<=' | '>' | '>=';\n\nconst COMPARISON_OPERATORS: ReadonlySet<string> = new Set([\n '!==',\n '<',\n '<=',\n '>',\n '>=',\n] as const satisfies readonly ComparisonOperator[]);\n\n/** Mirror of each comparison when its operands are swapped. */\nconst FLIPPED_OPERATORS = {\n '!==': '!==',\n '<': '>',\n '<=': '>=',\n '>': '<',\n '>=': '<=',\n} as const satisfies Record<ComparisonOperator, ComparisonOperator>;\n\n/** Classifies `index OP N` (a comparison against an integer literal). */\nconst classifyLiteralComparison = (\n op: ComparisonOperator,\n n: number,\n): CopyOp | undefined => {\n switch (op) {\n case '>=':\n return normalizeCountedOp('skip', n);\n\n case '>':\n return normalizeCountedOp('skip', n + 1);\n\n case '!==':\n return n === 0 ? { fn: 'tail' } : undefined;\n\n case '<':\n return normalizeCountedOp('take', n);\n\n case '<=':\n return normalizeCountedOp('take', n + 1);\n }\n};\n\n/** Classifies `index OP <xs>.length - M`. */\nconst classifyLengthComparison = (\n op: ComparisonOperator,\n m: number,\n): CopyOp | undefined => {\n switch (op) {\n case '<':\n return normalizeCountedOp('skipLast', m);\n\n case '<=':\n return normalizeCountedOp('skipLast', m - 1);\n\n case '!==':\n return m === 1 ? { fn: 'butLast' } : undefined;\n\n case '>=':\n return normalizeCountedOp('takeLast', m);\n\n case '>':\n return normalizeCountedOp('takeLast', m - 1);\n }\n};\n\nexport const preferCanonicalArraySlicing: TSESLint.RuleModule<\n MessageIds,\n Options\n> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Unify non-mutating array element addition/removal patterns (`slice` / `toSpliced` / `filter` / `concat` / spread) into the corresponding ts-data-forge functions: `Arr.tail`, `Arr.skip`, `Arr.take`, `Arr.butLast`, `Arr.skipLast`, `Arr.takeLast`, `Arr.toUnshifted`, `Arr.toPushed`.',\n },\n fixable: 'code',\n schema: [],\n messages: {\n preferCanonicalArraySlicing:\n 'Replace with `{{replacement}}` from ts-data-forge.',\n },\n },\n\n create: (context) => {\n const sourceCode = context.sourceCode;\n\n const program = sourceCode.ast;\n\n const tsDataForgeImport = getTsDataForgeImport(program);\n\n const parserServices = ESLintUtils.getParserServices(context);\n\n const checker = parserServices.program.getTypeChecker();\n\n /**\n * The in-scope way to reference the `Arr` namespace: a named import\n * (possibly aliased), a namespace import member, or the canonical `Arr`\n * (added by the import fix when nothing is imported yet).\n */\n const arrLocalName = getImportedLocalName(tsDataForgeImport, 'Arr');\n\n const namespaceLocalName = tsDataForgeImport?.specifiers.find(\n (specifier) => specifier.type === AST_NODE_TYPES.ImportNamespaceSpecifier,\n )?.local.name;\n\n const arrRef =\n arrLocalName ??\n (namespaceLocalName === undefined ? 'Arr' : `${namespaceLocalName}.Arr`);\n\n const needsArrImport =\n arrLocalName === undefined && namespaceLocalName === undefined;\n\n /**\n * Deferred (non-concrete) types are reduced to their base constraint\n * before checking; `any` / `unknown` are conservatively not array-like.\n */\n const isArrayOrTupleType = (type: ts.Type): boolean => {\n if ((type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) {\n return false;\n }\n\n if (type.isUnion()) {\n return type.types.every(isArrayOrTupleType);\n }\n\n if (\n (type.flags &\n (ts.TypeFlags.TypeParameter |\n ts.TypeFlags.IndexedAccess |\n ts.TypeFlags.Conditional |\n ts.TypeFlags.Substitution)) !==\n 0\n ) {\n const constraint = checker.getBaseConstraintOfType(type);\n\n return constraint !== undefined && isArrayOrTupleType(constraint);\n }\n\n return checker.isArrayType(type) || checker.isTupleType(type);\n };\n\n const getTypeOf = (node: TSESTree.Node): ts.Type =>\n checker.getTypeAtLocation(parserServices.esTreeNodeToTSNodeMap.get(node));\n\n /**\n * `true` only when the type can never be an array in any branch, so\n * `xs.concat(v)` is guaranteed to append `v` as one element rather than\n * flattening it.\n */\n const isDefinitelyNotArrayType = (type: ts.Type): boolean => {\n if ((type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) {\n return false;\n }\n\n if (type.isUnion()) {\n return type.types.every(isDefinitelyNotArrayType);\n }\n\n if (\n (type.flags &\n (ts.TypeFlags.TypeParameter |\n ts.TypeFlags.IndexedAccess |\n ts.TypeFlags.Conditional |\n ts.TypeFlags.Substitution)) !==\n 0\n ) {\n const constraint = checker.getBaseConstraintOfType(type);\n\n return constraint !== undefined && isDefinitelyNotArrayType(constraint);\n }\n\n return !checker.isArrayType(type) && !checker.isTupleType(type);\n };\n\n /** Argument-position text; a comma (sequence) expression needs parens. */\n const toArgText = (node: TSESTree.Node): string => {\n const text = sourceCode.getText(node);\n\n return node.type === AST_NODE_TYPES.SequenceExpression\n ? `(${text})`\n : text;\n };\n\n const toReplacementText = (op: CopyOp, xsText: string): string => {\n switch (op.fn) {\n case 'tail':\n case 'butLast':\n return `${arrRef}.${op.fn}(${xsText})`;\n\n case 'skip':\n case 'take':\n case 'skipLast':\n case 'takeLast':\n return `${arrRef}.${op.fn}(${xsText}, ${op.count})`;\n\n // Emit the curried form for `toUnshifted` so the new element stays in\n // the same textual position as in the `[item, ...xs]` source, keeping\n // the rewrite easy to read.\n case 'toUnshifted':\n return `${arrRef}.toUnshifted(${op.itemText})(${xsText})`;\n\n case 'toPushed':\n return `${arrRef}.toPushed(${xsText}, ${op.itemText})`;\n }\n };\n\n const mut_nodesToFix: {\n node: TSESTree.Node;\n replacement: string;\n }[] = [];\n\n const report = (node: TSESTree.Node, op: CopyOp, xsText: string): void => {\n mut_nodesToFix.push({\n node,\n replacement: toReplacementText(op, xsText),\n });\n };\n\n /** Matches `<xsText>.length` (non-computed). */\n const isReceiverLengthAccess = (\n node: TSESTree.Node,\n receiverText: string,\n ): boolean =>\n node.type === AST_NODE_TYPES.MemberExpression &&\n !node.computed &&\n node.property.type === AST_NODE_TYPES.Identifier &&\n node.property.name === 'length' &&\n sourceCode.getText(node.object) === receiverText;\n\n /** Matches `<xsText>.length - M` (M ≥ 1) and returns `M`. */\n const matchLengthMinus = (\n node: TSESTree.Node,\n receiverText: string,\n ): number | undefined => {\n if (\n node.type !== AST_NODE_TYPES.BinaryExpression ||\n node.operator !== '-' ||\n !isReceiverLengthAccess(node.left, receiverText)\n ) {\n return undefined;\n }\n\n const m = asNonNegativeIntegerLiteral(node.right);\n\n return m !== undefined && m >= 1 ? m : undefined;\n };\n\n /**\n * Matches an index-only filter callback (`(_, i) => <comparison>`) and\n * classifies the comparison. Both a leading index (`i >= 1`) and a\n * trailing index (`1 <= i`) operand order are accepted.\n */\n const classifyFilterCallback = (\n callback: TSESTree.CallExpressionArgument,\n receiver: TSESTree.Expression,\n receiverText: string,\n ): CopyOp | undefined => {\n if (\n callback.type !== AST_NODE_TYPES.ArrowFunctionExpression ||\n callback.async ||\n callback.body.type === AST_NODE_TYPES.BlockStatement ||\n callback.params.length !== 2\n ) {\n return undefined;\n }\n\n const [elementParam, indexParam] = callback.params;\n\n if (\n elementParam === undefined ||\n indexParam === undefined ||\n elementParam.type !== AST_NODE_TYPES.Identifier ||\n indexParam.type !== AST_NODE_TYPES.Identifier\n ) {\n return undefined;\n }\n\n // A callback parameter shadowing the receiver's root binding (e.g.\n // `xs.filter((xs, i) => i < xs.length - 1)`) would make the textual\n // `xs.length` comparison refer to the element, not the receiver.\n const receiverRoot = rootIdentifierName(receiver);\n\n if (\n receiverRoot !== undefined &&\n (elementParam.name === receiverRoot || indexParam.name === receiverRoot)\n ) {\n return undefined;\n }\n\n const body = callback.body;\n\n if (\n body.type !== AST_NODE_TYPES.BinaryExpression ||\n !COMPARISON_OPERATORS.has(body.operator)\n ) {\n return undefined;\n }\n\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n const rawOperator = body.operator as ComparisonOperator;\n\n const isIndex = (side: TSESTree.Node): boolean =>\n side.type === AST_NODE_TYPES.Identifier &&\n side.name === indexParam.name;\n\n const [op, other] = isIndex(body.left)\n ? ([rawOperator, body.right] as const)\n : isIndex(body.right)\n ? ([FLIPPED_OPERATORS[rawOperator], body.left] as const)\n : [undefined, undefined];\n\n // `op` and `other` are set together (both from the same tuple), so\n // checking `op` also guarantees `other` is defined.\n if (op === undefined || isIndex(other)) {\n return undefined;\n }\n\n // The element parameter must not participate in the comparison (only\n // the index side and a receiver-independent bound are recognized, so\n // reaching here with an element reference is impossible for literals,\n // but `xs.length - N` needs a stable receiver).\n const n = asNonNegativeIntegerLiteral(other);\n\n if (n !== undefined) {\n return classifyLiteralComparison(op, n);\n }\n\n if (!isSimpleReference(receiver)) return undefined;\n\n const m = matchLengthMinus(other, receiverText);\n\n return m === undefined ? undefined : classifyLengthComparison(op, m);\n };\n\n const classifySliceArgs = (\n args: readonly TSESTree.CallExpressionArgument[],\n receiver: TSESTree.Expression,\n ): CopyOp | undefined => {\n if (args.length === 1) {\n const [start] = args;\n\n if (start === undefined) return undefined;\n\n const n = asNonNegativeIntegerLiteral(start);\n\n if (n !== undefined) return normalizeCountedOp('skip', n);\n\n const negated = asNegatedPositiveIntegerLiteral(start);\n\n return negated === undefined\n ? undefined\n : normalizeCountedOp('takeLast', negated);\n }\n\n if (args.length === 2) {\n const [start, end] = args;\n\n if (\n start === undefined ||\n end === undefined ||\n asNonNegativeIntegerLiteral(start) !== 0\n ) {\n return undefined;\n }\n\n const n = asNonNegativeIntegerLiteral(end);\n\n if (n !== undefined) return normalizeCountedOp('take', n);\n\n const negated = asNegatedPositiveIntegerLiteral(end);\n\n if (negated !== undefined) {\n return normalizeCountedOp('skipLast', negated);\n }\n\n if (!isSimpleReference(receiver)) return undefined;\n\n const m = matchLengthMinus(end, sourceCode.getText(receiver));\n\n return m === undefined ? undefined : normalizeCountedOp('skipLast', m);\n }\n\n return undefined;\n };\n\n const classifyToSplicedArgs = (\n args: readonly TSESTree.CallExpressionArgument[],\n receiver: TSESTree.Expression,\n receiverText: string,\n ): CopyOp | undefined => {\n if (args.length === 1) {\n const [start] = args;\n\n if (start === undefined) return undefined;\n\n const n = asNonNegativeIntegerLiteral(start);\n\n // `xs.toSpliced(N)` removes everything from index N on → take N.\n if (n !== undefined) return normalizeCountedOp('take', n);\n\n const negated = asNegatedPositiveIntegerLiteral(start);\n\n // `xs.toSpliced(-N)` removes the last N elements → skipLast N.\n return negated === undefined\n ? undefined\n : normalizeCountedOp('skipLast', negated);\n }\n\n if (args.length === 2) {\n const [start, deleteCount] = args;\n\n if (\n start === undefined ||\n deleteCount === undefined ||\n asNonNegativeIntegerLiteral(start) !== 0\n ) {\n return undefined;\n }\n\n // `xs.toSpliced(0, N)` removes the first N elements → skip N.\n const n = asNonNegativeIntegerLiteral(deleteCount);\n\n if (n !== undefined) return normalizeCountedOp('skip', n);\n\n // `xs.toSpliced(0, xs.length - N)` keeps the last N → takeLast N.\n // (Note: `xs.toSpliced(0, -N)` clamps the delete count to 0 and is a\n // plain copy, so it is deliberately NOT treated as takeLast.)\n if (!isSimpleReference(receiver)) return undefined;\n\n const m = matchLengthMinus(deleteCount, receiverText);\n\n return m === undefined ? undefined : normalizeCountedOp('takeLast', m);\n }\n\n if (args.length === 3) {\n const [start, deleteCount, item] = args;\n\n if (\n start === undefined ||\n deleteCount === undefined ||\n item === undefined ||\n item.type === AST_NODE_TYPES.SpreadElement ||\n asNonNegativeIntegerLiteral(deleteCount) !== 0\n ) {\n return undefined;\n }\n\n // `xs.toSpliced(0, 0, item)` prepends → toUnshifted.\n if (asNonNegativeIntegerLiteral(start) === 0) {\n return { fn: 'toUnshifted', itemText: toArgText(item) };\n }\n\n // `xs.toSpliced(xs.length, 0, item)` appends → toPushed.\n if (\n isSimpleReference(receiver) &&\n isReceiverLengthAccess(start, receiverText)\n ) {\n return { fn: 'toPushed', itemText: toArgText(item) };\n }\n\n return undefined;\n }\n\n return undefined;\n };\n\n return {\n CallExpression: (node) => {\n const callee = node.callee;\n\n if (\n callee.type !== AST_NODE_TYPES.MemberExpression ||\n callee.computed ||\n callee.property.type !== AST_NODE_TYPES.Identifier\n ) {\n return;\n }\n\n const methodName = callee.property.name;\n\n const receiver = callee.object;\n\n // `Arr.skip(xs, 1)` → `Arr.tail(xs)`, `Arr.skipLast(xs, 1)` →\n // `Arr.butLast(xs)` (also via a namespace import or an alias).\n if (methodName === 'skip' || methodName === 'skipLast') {\n const isArrObject =\n (receiver.type === AST_NODE_TYPES.Identifier &&\n receiver.name === arrLocalName) ||\n (receiver.type === AST_NODE_TYPES.MemberExpression &&\n !receiver.computed &&\n receiver.object.type === AST_NODE_TYPES.Identifier &&\n receiver.object.name === namespaceLocalName &&\n receiver.property.type === AST_NODE_TYPES.Identifier &&\n receiver.property.name === 'Arr');\n\n const [arrayArg, countArg] = node.arguments;\n\n if (\n isArrObject &&\n node.arguments.length === 2 &&\n arrayArg !== undefined &&\n arrayArg.type !== AST_NODE_TYPES.SpreadElement &&\n countArg !== undefined &&\n asNonNegativeIntegerLiteral(countArg) === 1\n ) {\n report(\n node,\n { fn: methodName === 'skip' ? 'tail' : 'butLast' },\n toArgText(arrayArg),\n );\n }\n\n return;\n }\n\n // `[item].concat(xs)` → `Arr.toUnshifted(xs, item)`.\n if (\n methodName === 'concat' &&\n receiver.type === AST_NODE_TYPES.ArrayExpression\n ) {\n const [element] = receiver.elements;\n\n const [arg] = node.arguments;\n\n if (\n receiver.elements.length === 1 &&\n element !== null &&\n element !== undefined &&\n element.type !== AST_NODE_TYPES.SpreadElement &&\n node.arguments.length === 1 &&\n arg !== undefined &&\n arg.type !== AST_NODE_TYPES.SpreadElement &&\n isArrayOrTupleType(getTypeOf(arg))\n ) {\n report(\n node,\n { fn: 'toUnshifted', itemText: toArgText(element) },\n toArgText(arg),\n );\n }\n\n return;\n }\n\n if (\n methodName !== 'slice' &&\n methodName !== 'toSpliced' &&\n methodName !== 'filter' &&\n methodName !== 'concat'\n ) {\n return;\n }\n\n // The receiver must be an array or tuple (not a string / typed array\n // / other object that happens to share the method name).\n if (!isArrayOrTupleType(getTypeOf(receiver))) return;\n\n const receiverText = sourceCode.getText(receiver);\n\n switch (methodName) {\n case 'slice': {\n const op = classifySliceArgs(node.arguments, receiver);\n\n if (op !== undefined) report(node, op, toArgText(receiver));\n\n return;\n }\n\n case 'toSpliced': {\n const op = classifyToSplicedArgs(\n node.arguments,\n receiver,\n receiverText,\n );\n\n if (op !== undefined) report(node, op, toArgText(receiver));\n\n return;\n }\n\n case 'filter': {\n const [callback] = node.arguments;\n\n if (callback === undefined || node.arguments.length !== 1) {\n return;\n }\n\n const op = classifyFilterCallback(callback, receiver, receiverText);\n\n if (op !== undefined) report(node, op, toArgText(receiver));\n\n return;\n }\n\n case 'concat': {\n const [arg] = node.arguments;\n\n if (\n node.arguments.length !== 1 ||\n arg === undefined ||\n arg.type === AST_NODE_TYPES.SpreadElement\n ) {\n return;\n }\n\n // `xs.concat([item])` → `Arr.toPushed(xs, item)`.\n if (arg.type === AST_NODE_TYPES.ArrayExpression) {\n const [element] = arg.elements;\n\n if (\n arg.elements.length === 1 &&\n element !== null &&\n element !== undefined &&\n element.type !== AST_NODE_TYPES.SpreadElement\n ) {\n report(\n node,\n { fn: 'toPushed', itemText: toArgText(element) },\n toArgText(receiver),\n );\n }\n\n return;\n }\n\n // `xs.concat(item)` appends only when `item` can never be an\n // array (otherwise `concat` would flatten it).\n if (isDefinitelyNotArrayType(getTypeOf(arg))) {\n report(\n node,\n { fn: 'toPushed', itemText: toArgText(arg) },\n toArgText(receiver),\n );\n }\n }\n }\n },\n\n ArrayExpression: (node) => {\n // `[item, ...xs]` → `Arr.toUnshifted(xs, item)`,\n // `[...xs, item]` → `Arr.toPushed(xs, item)`.\n if (node.elements.length !== 2) return;\n\n const [first, second] = node.elements;\n\n if (\n first === null ||\n first === undefined ||\n second === null ||\n second === undefined\n ) {\n return;\n }\n\n const [spread, item, fn] =\n first.type === AST_NODE_TYPES.SpreadElement &&\n second.type !== AST_NODE_TYPES.SpreadElement\n ? ([first, second, 'toPushed'] as const)\n : first.type !== AST_NODE_TYPES.SpreadElement &&\n second.type === AST_NODE_TYPES.SpreadElement\n ? ([second, first, 'toUnshifted'] as const)\n : [undefined, undefined, undefined];\n\n // `spread`, `item`, and `fn` are set together (all from the same\n // tuple), so checking `spread` also guarantees the others are\n // defined.\n if (spread === undefined) return;\n\n // Spreading a non-array iterable (`[v, ...someSet]`) is a genuine\n // conversion, not an element addition to an array.\n if (!isArrayOrTupleType(getTypeOf(spread.argument))) return;\n\n report(\n node,\n { fn, itemText: toArgText(item) },\n toArgText(spread.argument),\n );\n },\n\n 'Program:exit': () => {\n for (const [index, nodeInfo] of mut_nodesToFix.entries()) {\n context.report({\n node: nodeInfo.node,\n messageId: 'preferCanonicalArraySlicing',\n data: { replacement: nodeInfo.replacement },\n fix: (fixer) => {\n const importFixes =\n index === 0 && needsArrImport\n ? buildImportFixes(fixer, program, tsDataForgeImport, ['Arr'])\n : [];\n\n return [\n ...importFixes,\n fixer.replaceText(nodeInfo.node, nodeInfo.replacement),\n ];\n },\n });\n }\n },\n };\n },\n defaultOptions: [],\n} as const;\n"],"names":[],"mappings":";;;;AAoCA,MAAM,kBAAA,GAAqB,CACzB,EAAA,EACA,KAAA,KAEA,KAAA,GAAQ,IACJ,MAAA,GACA,EAAA,KAAO,MAAA,IAAU,KAAA,KAAU,CAAA,GACxB,EAAE,IAAI,MAAA,EAAO,GACd,EAAA,KAAO,UAAA,IAAc,KAAA,KAAU,CAAA,GAC5B,EAAE,EAAA,EAAI,SAAA,EAAU,GAChB,EAAE,EAAA,EAAI,KAAA,EAAM;AAEvB,MAAM,2BAAA,GAA8B,CAClC,IAAA,KAEA,IAAA,CAAK,SAAS,cAAA,CAAe,OAAA,IAC7B,OAAO,IAAA,CAAK,KAAA,KAAU,YACtB,MAAA,CAAO,SAAA,CAAU,KAAK,KAAK,CAAA,IAC3B,KAAK,KAAA,IAAS,CAAA,GACV,KAAK,KAAA,GACL,MAAA;AAGN,MAAM,+BAAA,GAAkC,CACtC,IAAA,KACuB;AACvB,EAAA,IACE,IAAA,CAAK,IAAA,KAAS,cAAA,CAAe,eAAA,IAC7B,IAAA,CAAK,QAAA,KAAa,GAAA,IAClB,IAAA,CAAK,QAAA,CAAS,IAAA,KAAS,cAAA,CAAe,OAAA,EACtC;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,CAAA,GAAI,2BAAA,CAA4B,IAAA,CAAK,QAAQ,CAAA;AAEnD,EAAA,OAAO,CAAA,KAAM,MAAA,IAAa,CAAA,IAAK,CAAA,GAAI,CAAA,GAAI,MAAA;AACzC,CAAA;AAOA,MAAM,iBAAA,GAAoB,CAAC,IAAA,KACzB,IAAA,CAAK,IAAA,KAAS,eAAe,UAAA,IAC7B,IAAA,CAAK,IAAA,KAAS,cAAA,CAAe,cAAA,IAC5B,IAAA,CAAK,SAAS,cAAA,CAAe,gBAAA,IAC5B,CAAC,IAAA,CAAK,QAAA,IACN,IAAA,CAAK,QAAA,CAAS,IAAA,KAAS,cAAA,CAAe,UAAA,IACtC,iBAAA,CAAkB,IAAA,CAAK,MAAM,CAAA;AAEjC,MAAM,qBAAqB,CAAC,IAAA,KAC1B,IAAA,CAAK,IAAA,KAAS,eAAe,UAAA,GACzB,IAAA,CAAK,IAAA,GACL,IAAA,CAAK,SAAS,cAAA,CAAe,gBAAA,GAC3B,kBAAA,CAAmB,IAAA,CAAK,MAAM,CAAA,GAC9B,MAAA;AAIR,MAAM,oBAAA,uBAAgD,GAAA,CAAI;AAAA,EACxD,KAAA;AAAA,EACA,GAAA;AAAA,EACA,IAAA;AAAA,EACA,GAAA;AAAA,EACA;AACF,CAAkD,CAAA;AAGlD,MAAM,iBAAA,GAAoB;AAAA,EACxB,KAAA,EAAO,KAAA;AAAA,EACP,GAAA,EAAK,GAAA;AAAA,EACL,IAAA,EAAM,IAAA;AAAA,EACN,GAAA,EAAK,GAAA;AAAA,EACL,IAAA,EAAM;AACR,CAAA;AAGA,MAAM,yBAAA,GAA4B,CAChC,EAAA,EACA,CAAA,KACuB;AACvB,EAAA,QAAQ,EAAA;AAAI,IACV,KAAK,IAAA;AACH,MAAA,OAAO,kBAAA,CAAmB,QAAQ,CAAC,CAAA;AAAA,IAErC,KAAK,GAAA;AACH,MAAA,OAAO,kBAAA,CAAmB,MAAA,EAAQ,CAAA,GAAI,CAAC,CAAA;AAAA,IAEzC,KAAK,KAAA;AACH,MAAA,OAAO,CAAA,KAAM,CAAA,GAAI,EAAE,EAAA,EAAI,QAAO,GAAI,MAAA;AAAA,IAEpC,KAAK,GAAA;AACH,MAAA,OAAO,kBAAA,CAAmB,QAAQ,CAAC,CAAA;AAAA,IAErC,KAAK,IAAA;AACH,MAAA,OAAO,kBAAA,CAAmB,MAAA,EAAQ,CAAA,GAAI,CAAC,CAAA;AAAA;AAE7C,CAAA;AAGA,MAAM,wBAAA,GAA2B,CAC/B,EAAA,EACA,CAAA,KACuB;AACvB,EAAA,QAAQ,EAAA;AAAI,IACV,KAAK,GAAA;AACH,MAAA,OAAO,kBAAA,CAAmB,YAAY,CAAC,CAAA;AAAA,IAEzC,KAAK,IAAA;AACH,MAAA,OAAO,kBAAA,CAAmB,UAAA,EAAY,CAAA,GAAI,CAAC,CAAA;AAAA,IAE7C,KAAK,KAAA;AACH,MAAA,OAAO,CAAA,KAAM,CAAA,GAAI,EAAE,EAAA,EAAI,WAAU,GAAI,MAAA;AAAA,IAEvC,KAAK,IAAA;AACH,MAAA,OAAO,kBAAA,CAAmB,YAAY,CAAC,CAAA;AAAA,IAEzC,KAAK,GAAA;AACH,MAAA,OAAO,kBAAA,CAAmB,UAAA,EAAY,CAAA,GAAI,CAAC,CAAA;AAAA;AAEjD,CAAA;AAEO,MAAM,2BAAA,GAGT;AAAA,EACF,IAAA,EAAM;AAAA,IACJ,IAAA,EAAM,YAAA;AAAA,IACN,IAAA,EAAM;AAAA,MACJ,WAAA,EACE;AAAA,KACJ;AAAA,IACA,OAAA,EAAS,MAAA;AAAA,IACT,QAAQ,EAAC;AAAA,IACT,QAAA,EAAU;AAAA,MACR,2BAAA,EACE;AAAA;AACJ,GACF;AAAA,EAEA,MAAA,EAAQ,CAAC,OAAA,KAAY;AACnB,IAAA,MAAM,aAAa,OAAA,CAAQ,UAAA;AAE3B,IAAA,MAAM,UAAU,UAAA,CAAW,GAAA;AAE3B,IAAA,MAAM,iBAAA,GAAoB,qBAAqB,OAAO,CAAA;AAEtD,IAAA,MAAM,cAAA,GAAiB,WAAA,CAAY,iBAAA,CAAkB,OAAO,CAAA;AAE5D,IAAA,MAAM,OAAA,GAAU,cAAA,CAAe,OAAA,CAAQ,cAAA,EAAe;AAOtD,IAAA,MAAM,YAAA,GAAe,oBAAA,CAAqB,iBAAA,EAAmB,KAAK,CAAA;AAElE,IAAA,MAAM,kBAAA,GAAqB,mBAAmB,UAAA,CAAW,IAAA;AAAA,MACvD,CAAC,SAAA,KAAc,SAAA,CAAU,IAAA,KAAS,cAAA,CAAe;AAAA,OAChD,KAAA,CAAM,IAAA;AAET,IAAA,MAAM,SACJ,YAAA,KACC,kBAAA,KAAuB,MAAA,GAAY,KAAA,GAAQ,GAAG,kBAAkB,CAAA,IAAA,CAAA,CAAA;AAEnE,IAAA,MAAM,cAAA,GACJ,YAAA,KAAiB,MAAA,IAAa,kBAAA,KAAuB,MAAA;AAMvD,IAAA,MAAM,kBAAA,GAAqB,CAAC,IAAA,KAA2B;AACrD,MAAA,IAAA,CAAK,IAAA,CAAK,SAAS,EAAA,CAAG,SAAA,CAAU,MAAM,EAAA,CAAG,SAAA,CAAU,cAAc,CAAA,EAAG;AAClE,QAAA,OAAO,KAAA;AAAA,MACT;AAEA,MAAA,IAAI,IAAA,CAAK,SAAQ,EAAG;AAClB,QAAA,OAAO,IAAA,CAAK,KAAA,CAAM,KAAA,CAAM,kBAAkB,CAAA;AAAA,MAC5C;AAEA,MAAA,IAAA,CACG,IAAA,CAAK,KAAA,IACH,EAAA,CAAG,SAAA,CAAU,gBACZ,EAAA,CAAG,SAAA,CAAU,aAAA,GACb,EAAA,CAAG,SAAA,CAAU,WAAA,GACb,EAAA,CAAG,SAAA,CAAU,mBACjB,CAAA,EACA;AACA,QAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,uBAAA,CAAwB,IAAI,CAAA;AAEvD,QAAA,OAAO,UAAA,KAAe,MAAA,IAAa,kBAAA,CAAmB,UAAU,CAAA;AAAA,MAClE;AAEA,MAAA,OAAO,QAAQ,WAAA,CAAY,IAAI,CAAA,IAAK,OAAA,CAAQ,YAAY,IAAI,CAAA;AAAA,IAC9D,CAAA;AAEA,IAAA,MAAM,SAAA,GAAY,CAAC,IAAA,KACjB,OAAA,CAAQ,kBAAkB,cAAA,CAAe,qBAAA,CAAsB,GAAA,CAAI,IAAI,CAAC,CAAA;AAO1E,IAAA,MAAM,wBAAA,GAA2B,CAAC,IAAA,KAA2B;AAC3D,MAAA,IAAA,CAAK,IAAA,CAAK,SAAS,EAAA,CAAG,SAAA,CAAU,MAAM,EAAA,CAAG,SAAA,CAAU,cAAc,CAAA,EAAG;AAClE,QAAA,OAAO,KAAA;AAAA,MACT;AAEA,MAAA,IAAI,IAAA,CAAK,SAAQ,EAAG;AAClB,QAAA,OAAO,IAAA,CAAK,KAAA,CAAM,KAAA,CAAM,wBAAwB,CAAA;AAAA,MAClD;AAEA,MAAA,IAAA,CACG,IAAA,CAAK,KAAA,IACH,EAAA,CAAG,SAAA,CAAU,gBACZ,EAAA,CAAG,SAAA,CAAU,aAAA,GACb,EAAA,CAAG,SAAA,CAAU,WAAA,GACb,EAAA,CAAG,SAAA,CAAU,mBACjB,CAAA,EACA;AACA,QAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,uBAAA,CAAwB,IAAI,CAAA;AAEvD,QAAA,OAAO,UAAA,KAAe,MAAA,IAAa,wBAAA,CAAyB,UAAU,CAAA;AAAA,MACxE;AAEA,MAAA,OAAO,CAAC,QAAQ,WAAA,CAAY,IAAI,KAAK,CAAC,OAAA,CAAQ,YAAY,IAAI,CAAA;AAAA,IAChE,CAAA;AAGA,IAAA,MAAM,SAAA,GAAY,CAAC,IAAA,KAAgC;AACjD,MAAA,MAAM,IAAA,GAAO,UAAA,CAAW,OAAA,CAAQ,IAAI,CAAA;AAEpC,MAAA,OAAO,KAAK,IAAA,KAAS,cAAA,CAAe,kBAAA,GAChC,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA,CAAA,GACR,IAAA;AAAA,IACN,CAAA;AAEA,IAAA,MAAM,iBAAA,GAAoB,CAAC,EAAA,EAAY,MAAA,KAA2B;AAChE,MAAA,QAAQ,GAAG,EAAA;AAAI,QACb,KAAK,MAAA;AAAA,QACL,KAAK,SAAA;AACH,UAAA,OAAO,GAAG,MAAM,CAAA,CAAA,EAAI,EAAA,CAAG,EAAE,IAAI,MAAM,CAAA,CAAA,CAAA;AAAA,QAErC,KAAK,MAAA;AAAA,QACL,KAAK,MAAA;AAAA,QACL,KAAK,UAAA;AAAA,QACL,KAAK,UAAA;AACH,UAAA,OAAO,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,EAAA,CAAG,EAAE,CAAA,CAAA,EAAI,MAAM,CAAA,EAAA,EAAK,EAAA,CAAG,KAAK,CAAA,CAAA,CAAA;AAAA;AAAA;AAAA;AAAA,QAKlD,KAAK,aAAA;AACH,UAAA,OAAO,GAAG,MAAM,CAAA,aAAA,EAAgB,EAAA,CAAG,QAAQ,KAAK,MAAM,CAAA,CAAA,CAAA;AAAA,QAExD,KAAK,UAAA;AACH,UAAA,OAAO,GAAG,MAAM,CAAA,UAAA,EAAa,MAAM,CAAA,EAAA,EAAK,GAAG,QAAQ,CAAA,CAAA,CAAA;AAAA;AACvD,IACF,CAAA;AAEA,IAAA,MAAM,iBAGA,EAAC;AAEP,IAAA,MAAM,MAAA,GAAS,CAAC,IAAA,EAAqB,EAAA,EAAY,MAAA,KAAyB;AACxE,MAAA,cAAA,CAAe,IAAA,CAAK;AAAA,QAClB,IAAA;AAAA,QACA,WAAA,EAAa,iBAAA,CAAkB,EAAA,EAAI,MAAM;AAAA,OAC1C,CAAA;AAAA,IACH,CAAA;AAGA,IAAA,MAAM,sBAAA,GAAyB,CAC7B,IAAA,EACA,YAAA,KAEA,IAAA,CAAK,SAAS,cAAA,CAAe,gBAAA,IAC7B,CAAC,IAAA,CAAK,QAAA,IACN,IAAA,CAAK,SAAS,IAAA,KAAS,cAAA,CAAe,UAAA,IACtC,IAAA,CAAK,QAAA,CAAS,IAAA,KAAS,YACvB,UAAA,CAAW,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAA,KAAM,YAAA;AAGtC,IAAA,MAAM,gBAAA,GAAmB,CACvB,IAAA,EACA,YAAA,KACuB;AACvB,MAAA,IACE,IAAA,CAAK,IAAA,KAAS,cAAA,CAAe,gBAAA,IAC7B,IAAA,CAAK,QAAA,KAAa,GAAA,IAClB,CAAC,sBAAA,CAAuB,IAAA,CAAK,IAAA,EAAM,YAAY,CAAA,EAC/C;AACA,QAAA,OAAO,MAAA;AAAA,MACT;AAEA,MAAA,MAAM,CAAA,GAAI,2BAAA,CAA4B,IAAA,CAAK,KAAK,CAAA;AAEhD,MAAA,OAAO,CAAA,KAAM,MAAA,IAAa,CAAA,IAAK,CAAA,GAAI,CAAA,GAAI,MAAA;AAAA,IACzC,CAAA;AAOA,IAAA,MAAM,sBAAA,GAAyB,CAC7B,QAAA,EACA,QAAA,EACA,YAAA,KACuB;AACvB,MAAA,IACE,QAAA,CAAS,IAAA,KAAS,cAAA,CAAe,uBAAA,IACjC,SAAS,KAAA,IACT,QAAA,CAAS,IAAA,CAAK,IAAA,KAAS,cAAA,CAAe,cAAA,IACtC,QAAA,CAAS,MAAA,CAAO,WAAW,CAAA,EAC3B;AACA,QAAA,OAAO,MAAA;AAAA,MACT;AAEA,MAAA,MAAM,CAAC,YAAA,EAAc,UAAU,CAAA,GAAI,QAAA,CAAS,MAAA;AAE5C,MAAA,IACE,YAAA,KAAiB,MAAA,IACjB,UAAA,KAAe,MAAA,IACf,YAAA,CAAa,IAAA,KAAS,cAAA,CAAe,UAAA,IACrC,UAAA,CAAW,IAAA,KAAS,cAAA,CAAe,UAAA,EACnC;AACA,QAAA,OAAO,MAAA;AAAA,MACT;AAKA,MAAA,MAAM,YAAA,GAAe,mBAAmB,QAAQ,CAAA;AAEhD,MAAA,IACE,iBAAiB,MAAA,KAChB,YAAA,CAAa,SAAS,YAAA,IAAgB,UAAA,CAAW,SAAS,YAAA,CAAA,EAC3D;AACA,QAAA,OAAO,MAAA;AAAA,MACT;AAEA,MAAA,MAAM,OAAO,QAAA,CAAS,IAAA;AAEtB,MAAA,IACE,IAAA,CAAK,SAAS,cAAA,CAAe,gBAAA,IAC7B,CAAC,oBAAA,CAAqB,GAAA,CAAI,IAAA,CAAK,QAAQ,CAAA,EACvC;AACA,QAAA,OAAO,MAAA;AAAA,MACT;AAGA,MAAA,MAAM,cAAc,IAAA,CAAK,QAAA;AAEzB,MAAA,MAAM,OAAA,GAAU,CAAC,IAAA,KACf,IAAA,CAAK,SAAS,cAAA,CAAe,UAAA,IAC7B,IAAA,CAAK,IAAA,KAAS,UAAA,CAAW,IAAA;AAE3B,MAAA,MAAM,CAAC,EAAA,EAAI,KAAK,CAAA,GAAI,OAAA,CAAQ,IAAA,CAAK,IAAI,CAAA,GAChC,CAAC,WAAA,EAAa,IAAA,CAAK,KAAK,CAAA,GACzB,QAAQ,IAAA,CAAK,KAAK,CAAA,GACf,CAAC,iBAAA,CAAkB,WAAW,CAAA,EAAG,IAAA,CAAK,IAAI,CAAA,GAC3C,CAAC,MAAA,EAAW,MAAS,CAAA;AAI3B,MAAA,IAAI,EAAA,KAAO,MAAA,IAAa,OAAA,CAAQ,KAAK,CAAA,EAAG;AACtC,QAAA,OAAO,MAAA;AAAA,MACT;AAMA,MAAA,MAAM,CAAA,GAAI,4BAA4B,KAAK,CAAA;AAE3C,MAAA,IAAI,MAAM,MAAA,EAAW;AACnB,QAAA,OAAO,yBAAA,CAA0B,IAAI,CAAC,CAAA;AAAA,MACxC;AAEA,MAAA,IAAI,CAAC,iBAAA,CAAkB,QAAQ,CAAA,EAAG,OAAO,MAAA;AAEzC,MAAA,MAAM,CAAA,GAAI,gBAAA,CAAiB,KAAA,EAAO,YAAY,CAAA;AAE9C,MAAA,OAAO,CAAA,KAAM,MAAA,GAAY,MAAA,GAAY,wBAAA,CAAyB,IAAI,CAAC,CAAA;AAAA,IACrE,CAAA;AAEA,IAAA,MAAM,iBAAA,GAAoB,CACxB,IAAA,EACA,QAAA,KACuB;AACvB,MAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG;AACrB,QAAA,MAAM,CAAC,KAAK,CAAA,GAAI,IAAA;AAEhB,QAAA,IAAI,KAAA,KAAU,QAAW,OAAO,MAAA;AAEhC,QAAA,MAAM,CAAA,GAAI,4BAA4B,KAAK,CAAA;AAE3C,QAAA,IAAI,CAAA,KAAM,MAAA,EAAW,OAAO,kBAAA,CAAmB,QAAQ,CAAC,CAAA;AAExD,QAAA,MAAM,OAAA,GAAU,gCAAgC,KAAK,CAAA;AAErD,QAAA,OAAO,OAAA,KAAY,MAAA,GACf,MAAA,GACA,kBAAA,CAAmB,YAAY,OAAO,CAAA;AAAA,MAC5C;AAEA,MAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG;AACrB,QAAA,MAAM,CAAC,KAAA,EAAO,GAAG,CAAA,GAAI,IAAA;AAErB,QAAA,IACE,UAAU,MAAA,IACV,GAAA,KAAQ,UACR,2BAAA,CAA4B,KAAK,MAAM,CAAA,EACvC;AACA,UAAA,OAAO,MAAA;AAAA,QACT;AAEA,QAAA,MAAM,CAAA,GAAI,4BAA4B,GAAG,CAAA;AAEzC,QAAA,IAAI,CAAA,KAAM,MAAA,EAAW,OAAO,kBAAA,CAAmB,QAAQ,CAAC,CAAA;AAExD,QAAA,MAAM,OAAA,GAAU,gCAAgC,GAAG,CAAA;AAEnD,QAAA,IAAI,YAAY,MAAA,EAAW;AACzB,UAAA,OAAO,kBAAA,CAAmB,YAAY,OAAO,CAAA;AAAA,QAC/C;AAEA,QAAA,IAAI,CAAC,iBAAA,CAAkB,QAAQ,CAAA,EAAG,OAAO,MAAA;AAEzC,QAAA,MAAM,IAAI,gBAAA,CAAiB,GAAA,EAAK,UAAA,CAAW,OAAA,CAAQ,QAAQ,CAAC,CAAA;AAE5D,QAAA,OAAO,CAAA,KAAM,MAAA,GAAY,MAAA,GAAY,kBAAA,CAAmB,YAAY,CAAC,CAAA;AAAA,MACvE;AAEA,MAAA,OAAO,MAAA;AAAA,IACT,CAAA;AAEA,IAAA,MAAM,qBAAA,GAAwB,CAC5B,IAAA,EACA,QAAA,EACA,YAAA,KACuB;AACvB,MAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG;AACrB,QAAA,MAAM,CAAC,KAAK,CAAA,GAAI,IAAA;AAEhB,QAAA,IAAI,KAAA,KAAU,QAAW,OAAO,MAAA;AAEhC,QAAA,MAAM,CAAA,GAAI,4BAA4B,KAAK,CAAA;AAG3C,QAAA,IAAI,CAAA,KAAM,MAAA,EAAW,OAAO,kBAAA,CAAmB,QAAQ,CAAC,CAAA;AAExD,QAAA,MAAM,OAAA,GAAU,gCAAgC,KAAK,CAAA;AAGrD,QAAA,OAAO,OAAA,KAAY,MAAA,GACf,MAAA,GACA,kBAAA,CAAmB,YAAY,OAAO,CAAA;AAAA,MAC5C;AAEA,MAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG;AACrB,QAAA,MAAM,CAAC,KAAA,EAAO,WAAW,CAAA,GAAI,IAAA;AAE7B,QAAA,IACE,UAAU,MAAA,IACV,WAAA,KAAgB,UAChB,2BAAA,CAA4B,KAAK,MAAM,CAAA,EACvC;AACA,UAAA,OAAO,MAAA;AAAA,QACT;AAGA,QAAA,MAAM,CAAA,GAAI,4BAA4B,WAAW,CAAA;AAEjD,QAAA,IAAI,CAAA,KAAM,MAAA,EAAW,OAAO,kBAAA,CAAmB,QAAQ,CAAC,CAAA;AAKxD,QAAA,IAAI,CAAC,iBAAA,CAAkB,QAAQ,CAAA,EAAG,OAAO,MAAA;AAEzC,QAAA,MAAM,CAAA,GAAI,gBAAA,CAAiB,WAAA,EAAa,YAAY,CAAA;AAEpD,QAAA,OAAO,CAAA,KAAM,MAAA,GAAY,MAAA,GAAY,kBAAA,CAAmB,YAAY,CAAC,CAAA;AAAA,MACvE;AAEA,MAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG;AACrB,QAAA,MAAM,CAAC,KAAA,EAAO,WAAA,EAAa,IAAI,CAAA,GAAI,IAAA;AAEnC,QAAA,IACE,KAAA,KAAU,MAAA,IACV,WAAA,KAAgB,MAAA,IAChB,IAAA,KAAS,MAAA,IACT,IAAA,CAAK,IAAA,KAAS,cAAA,CAAe,aAAA,IAC7B,2BAAA,CAA4B,WAAW,MAAM,CAAA,EAC7C;AACA,UAAA,OAAO,MAAA;AAAA,QACT;AAGA,QAAA,IAAI,2BAAA,CAA4B,KAAK,CAAA,KAAM,CAAA,EAAG;AAC5C,UAAA,OAAO,EAAE,EAAA,EAAI,aAAA,EAAe,QAAA,EAAU,SAAA,CAAU,IAAI,CAAA,EAAE;AAAA,QACxD;AAGA,QAAA,IACE,kBAAkB,QAAQ,CAAA,IAC1B,sBAAA,CAAuB,KAAA,EAAO,YAAY,CAAA,EAC1C;AACA,UAAA,OAAO,EAAE,EAAA,EAAI,UAAA,EAAY,QAAA,EAAU,SAAA,CAAU,IAAI,CAAA,EAAE;AAAA,QACrD;AAEA,QAAA,OAAO,MAAA;AAAA,MACT;AAEA,MAAA,OAAO,MAAA;AAAA,IACT,CAAA;AAEA,IAAA,OAAO;AAAA,MACL,cAAA,EAAgB,CAAC,IAAA,KAAS;AACxB,QAAA,MAAM,SAAS,IAAA,CAAK,MAAA;AAEpB,QAAA,IACE,MAAA,CAAO,IAAA,KAAS,cAAA,CAAe,gBAAA,IAC/B,MAAA,CAAO,YACP,MAAA,CAAO,QAAA,CAAS,IAAA,KAAS,cAAA,CAAe,UAAA,EACxC;AACA,UAAA;AAAA,QACF;AAEA,QAAA,MAAM,UAAA,GAAa,OAAO,QAAA,CAAS,IAAA;AAEnC,QAAA,MAAM,WAAW,MAAA,CAAO,MAAA;AAIxB,QAAA,IAAI,UAAA,KAAe,MAAA,IAAU,UAAA,KAAe,UAAA,EAAY;AACtD,UAAA,MAAM,WAAA,GACH,QAAA,CAAS,IAAA,KAAS,cAAA,CAAe,UAAA,IAChC,QAAA,CAAS,IAAA,KAAS,YAAA,IACnB,QAAA,CAAS,IAAA,KAAS,cAAA,CAAe,gBAAA,IAChC,CAAC,QAAA,CAAS,QAAA,IACV,QAAA,CAAS,MAAA,CAAO,IAAA,KAAS,cAAA,CAAe,UAAA,IACxC,QAAA,CAAS,OAAO,IAAA,KAAS,kBAAA,IACzB,QAAA,CAAS,QAAA,CAAS,IAAA,KAAS,cAAA,CAAe,UAAA,IAC1C,QAAA,CAAS,SAAS,IAAA,KAAS,KAAA;AAE/B,UAAA,MAAM,CAAC,QAAA,EAAU,QAAQ,CAAA,GAAI,IAAA,CAAK,SAAA;AAElC,UAAA,IACE,eACA,IAAA,CAAK,SAAA,CAAU,MAAA,KAAW,CAAA,IAC1B,aAAa,MAAA,IACb,QAAA,CAAS,IAAA,KAAS,cAAA,CAAe,iBACjC,QAAA,KAAa,MAAA,IACb,2BAAA,CAA4B,QAAQ,MAAM,CAAA,EAC1C;AACA,YAAA,MAAA;AAAA,cACE,IAAA;AAAA,cACA,EAAE,EAAA,EAAI,UAAA,KAAe,MAAA,GAAS,SAAS,SAAA,EAAU;AAAA,cACjD,UAAU,QAAQ;AAAA,aACpB;AAAA,UACF;AAEA,UAAA;AAAA,QACF;AAGA,QAAA,IACE,UAAA,KAAe,QAAA,IACf,QAAA,CAAS,IAAA,KAAS,eAAe,eAAA,EACjC;AACA,UAAA,MAAM,CAAC,OAAO,CAAA,GAAI,QAAA,CAAS,QAAA;AAE3B,UAAA,MAAM,CAAC,GAAG,CAAA,GAAI,IAAA,CAAK,SAAA;AAEnB,UAAA,IACE,QAAA,CAAS,QAAA,CAAS,MAAA,KAAW,CAAA,IAC7B,OAAA,KAAY,IAAA,IACZ,OAAA,KAAY,MAAA,IACZ,OAAA,CAAQ,IAAA,KAAS,cAAA,CAAe,aAAA,IAChC,IAAA,CAAK,UAAU,MAAA,KAAW,CAAA,IAC1B,GAAA,KAAQ,MAAA,IACR,GAAA,CAAI,IAAA,KAAS,cAAA,CAAe,aAAA,IAC5B,kBAAA,CAAmB,SAAA,CAAU,GAAG,CAAC,CAAA,EACjC;AACA,YAAA,MAAA;AAAA,cACE,IAAA;AAAA,cACA,EAAE,EAAA,EAAI,aAAA,EAAe,QAAA,EAAU,SAAA,CAAU,OAAO,CAAA,EAAE;AAAA,cAClD,UAAU,GAAG;AAAA,aACf;AAAA,UACF;AAEA,UAAA;AAAA,QACF;AAEA,QAAA,IACE,eAAe,OAAA,IACf,UAAA,KAAe,eACf,UAAA,KAAe,QAAA,IACf,eAAe,QAAA,EACf;AACA,UAAA;AAAA,QACF;AAIA,QAAA,IAAI,CAAC,kBAAA,CAAmB,SAAA,CAAU,QAAQ,CAAC,CAAA,EAAG;AAE9C,QAAA,MAAM,YAAA,GAAe,UAAA,CAAW,OAAA,CAAQ,QAAQ,CAAA;AAEhD,QAAA,QAAQ,UAAA;AAAY,UAClB,KAAK,OAAA,EAAS;AACZ,YAAA,MAAM,EAAA,GAAK,iBAAA,CAAkB,IAAA,CAAK,SAAA,EAAW,QAAQ,CAAA;AAErD,YAAA,IAAI,OAAO,MAAA,EAAW,MAAA,CAAO,MAAM,EAAA,EAAI,SAAA,CAAU,QAAQ,CAAC,CAAA;AAE1D,YAAA;AAAA,UACF;AAAA,UAEA,KAAK,WAAA,EAAa;AAChB,YAAA,MAAM,EAAA,GAAK,qBAAA;AAAA,cACT,IAAA,CAAK,SAAA;AAAA,cACL,QAAA;AAAA,cACA;AAAA,aACF;AAEA,YAAA,IAAI,OAAO,MAAA,EAAW,MAAA,CAAO,MAAM,EAAA,EAAI,SAAA,CAAU,QAAQ,CAAC,CAAA;AAE1D,YAAA;AAAA,UACF;AAAA,UAEA,KAAK,QAAA,EAAU;AACb,YAAA,MAAM,CAAC,QAAQ,CAAA,GAAI,IAAA,CAAK,SAAA;AAExB,YAAA,IAAI,QAAA,KAAa,MAAA,IAAa,IAAA,CAAK,SAAA,CAAU,WAAW,CAAA,EAAG;AACzD,cAAA;AAAA,YACF;AAEA,YAAA,MAAM,EAAA,GAAK,sBAAA,CAAuB,QAAA,EAAU,QAAA,EAAU,YAAY,CAAA;AAElE,YAAA,IAAI,OAAO,MAAA,EAAW,MAAA,CAAO,MAAM,EAAA,EAAI,SAAA,CAAU,QAAQ,CAAC,CAAA;AAE1D,YAAA;AAAA,UACF;AAAA,UAEA,KAAK,QAAA,EAAU;AACb,YAAA,MAAM,CAAC,GAAG,CAAA,GAAI,IAAA,CAAK,SAAA;AAEnB,YAAA,IACE,IAAA,CAAK,UAAU,MAAA,KAAW,CAAA,IAC1B,QAAQ,MAAA,IACR,GAAA,CAAI,IAAA,KAAS,cAAA,CAAe,aAAA,EAC5B;AACA,cAAA;AAAA,YACF;AAGA,YAAA,IAAI,GAAA,CAAI,IAAA,KAAS,cAAA,CAAe,eAAA,EAAiB;AAC/C,cAAA,MAAM,CAAC,OAAO,CAAA,GAAI,GAAA,CAAI,QAAA;AAEtB,cAAA,IACE,GAAA,CAAI,QAAA,CAAS,MAAA,KAAW,CAAA,IACxB,OAAA,KAAY,IAAA,IACZ,OAAA,KAAY,MAAA,IACZ,OAAA,CAAQ,IAAA,KAAS,cAAA,CAAe,aAAA,EAChC;AACA,gBAAA,MAAA;AAAA,kBACE,IAAA;AAAA,kBACA,EAAE,EAAA,EAAI,UAAA,EAAY,QAAA,EAAU,SAAA,CAAU,OAAO,CAAA,EAAE;AAAA,kBAC/C,UAAU,QAAQ;AAAA,iBACpB;AAAA,cACF;AAEA,cAAA;AAAA,YACF;AAIA,YAAA,IAAI,wBAAA,CAAyB,SAAA,CAAU,GAAG,CAAC,CAAA,EAAG;AAC5C,cAAA,MAAA;AAAA,gBACE,IAAA;AAAA,gBACA,EAAE,EAAA,EAAI,UAAA,EAAY,QAAA,EAAU,SAAA,CAAU,GAAG,CAAA,EAAE;AAAA,gBAC3C,UAAU,QAAQ;AAAA,eACpB;AAAA,YACF;AAAA,UACF;AAAA;AACF,MACF,CAAA;AAAA,MAEA,eAAA,EAAiB,CAAC,IAAA,KAAS;AAGzB,QAAA,IAAI,IAAA,CAAK,QAAA,CAAS,MAAA,KAAW,CAAA,EAAG;AAEhC,QAAA,MAAM,CAAC,KAAA,EAAO,MAAM,CAAA,GAAI,IAAA,CAAK,QAAA;AAE7B,QAAA,IACE,UAAU,IAAA,IACV,KAAA,KAAU,UACV,MAAA,KAAW,IAAA,IACX,WAAW,MAAA,EACX;AACA,UAAA;AAAA,QACF;AAEA,QAAA,MAAM,CAAC,MAAA,EAAQ,IAAA,EAAM,EAAE,IACrB,KAAA,CAAM,IAAA,KAAS,cAAA,CAAe,aAAA,IAC9B,OAAO,IAAA,KAAS,cAAA,CAAe,aAAA,GAC1B,CAAC,OAAO,MAAA,EAAQ,UAAU,CAAA,GAC3B,KAAA,CAAM,IAAA,KAAS,cAAA,CAAe,aAAA,IAC5B,MAAA,CAAO,SAAS,cAAA,CAAe,aAAA,GAC9B,CAAC,MAAA,EAAQ,OAAO,aAAa,CAAA,GAC9B,CAAC,MAAA,EAAW,QAAW,MAAS,CAAA;AAKxC,QAAA,IAAI,WAAW,MAAA,EAAW;AAI1B,QAAA,IAAI,CAAC,kBAAA,CAAmB,SAAA,CAAU,MAAA,CAAO,QAAQ,CAAC,CAAA,EAAG;AAErD,QAAA,MAAA;AAAA,UACE,IAAA;AAAA,UACA,EAAE,EAAA,EAAI,QAAA,EAAU,SAAA,CAAU,IAAI,CAAA,EAAE;AAAA,UAChC,SAAA,CAAU,OAAO,QAAQ;AAAA,SAC3B;AAAA,MACF,CAAA;AAAA,MAEA,gBAAgB,MAAM;AACpB,QAAA,KAAA,MAAW,CAAC,KAAA,EAAO,QAAQ,CAAA,IAAK,cAAA,CAAe,SAAQ,EAAG;AACxD,UAAA,OAAA,CAAQ,MAAA,CAAO;AAAA,YACb,MAAM,QAAA,CAAS,IAAA;AAAA,YACf,SAAA,EAAW,6BAAA;AAAA,YACX,IAAA,EAAM,EAAE,WAAA,EAAa,QAAA,CAAS,WAAA,EAAY;AAAA,YAC1C,GAAA,EAAK,CAAC,KAAA,KAAU;AACd,cAAA,MAAM,WAAA,GACJ,KAAA,KAAU,CAAA,IAAK,cAAA,GACX,gBAAA,CAAiB,KAAA,EAAO,OAAA,EAAS,iBAAA,EAAmB,CAAC,KAAK,CAAC,CAAA,GAC3D,EAAC;AAEP,cAAA,OAAO;AAAA,gBACL,GAAG,WAAA;AAAA,gBACH,KAAA,CAAM,WAAA,CAAY,QAAA,CAAS,IAAA,EAAM,SAAS,WAAW;AAAA,eACvD;AAAA,YACF;AAAA,WACD,CAAA;AAAA,QACH;AAAA,MACF;AAAA,KACF;AAAA,EACF,CAAA;AAAA,EACA,gBAAgB;AAClB;;;;"}
|
|
1
|
+
{"version":3,"file":"prefer-canonical-array-slicing.mjs","sources":["../../src/rules/prefer-canonical-array-slicing.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n ESLintUtils,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { type ReadonlyRecord } from 'ts-type-forge';\nimport * as ts from 'typescript';\nimport {\n buildImportFixes,\n getImportedLocalName,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\n/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */\n\ntype Options = readonly [];\n\ntype MessageIds = 'preferCanonicalArraySlicing';\n\n/**\n * A canonical ts-data-forge `Arr` operation a detected pattern is rewritten to:\n *\n * - `tail` / `butLast` — drop the first / last element\n * - `skip N` / `take N` — drop / keep the first `N` elements\n * - `skipLast N` / `takeLast N` — drop / keep the last `N` elements\n * - `toUnshifted item` / `toPushed item` — prepend / append one element\n * (`toUnshifted` is emitted in its curried form `toUnshifted(item)(xs)` so\n * the new element keeps the leading position it had in `[item, ...xs]`)\n */\ntype CopyOp = Readonly<\n | { fn: 'butLast' | 'tail' }\n | { fn: 'skip' | 'skipLast' | 'take' | 'takeLast'; count: number }\n | { fn: 'toPushed' | 'toUnshifted'; itemText: string }\n>;\n\n/** `skip 1` / `skipLast 1` have dedicated single-element names. */\nconst normalizeCountedOp = (\n fn: 'skip' | 'skipLast' | 'take' | 'takeLast',\n count: number,\n): CopyOp | undefined =>\n count < 1\n ? undefined\n : fn === 'skip' && count === 1\n ? ({ fn: 'tail' } as const)\n : fn === 'skipLast' && count === 1\n ? ({ fn: 'butLast' } as const)\n : ({ fn, count } as const);\n\nconst asNonNegativeIntegerLiteral = (\n node: TSESTree.Node,\n): number | undefined =>\n node.type === AST_NODE_TYPES.Literal &&\n typeof node.value === 'number' &&\n Number.isInteger(node.value) &&\n node.value >= 0\n ? node.value\n : undefined;\n\n/** Matches `-N` (unary minus of a positive integer literal) and returns `N`. */\nconst asNegatedPositiveIntegerLiteral = (\n node: TSESTree.Node,\n): number | undefined => {\n if (\n node.type !== AST_NODE_TYPES.UnaryExpression ||\n node.operator !== '-' ||\n node.argument.type !== AST_NODE_TYPES.Literal\n ) {\n return undefined;\n }\n\n const n = asNonNegativeIntegerLiteral(node.argument);\n\n return n !== undefined && n >= 1 ? n : undefined;\n};\n\n/**\n * Whether re-reading the expression is free of side effects (a plain variable\n * or a non-computed property chain). Required for patterns that reference the\n * receiver twice, such as `xs.filter((_, i) => i < xs.length - 1)`.\n */\nconst isSimpleReference = (node: TSESTree.Node): boolean =>\n node.type === AST_NODE_TYPES.Identifier ||\n node.type === AST_NODE_TYPES.ThisExpression ||\n (node.type === AST_NODE_TYPES.MemberExpression &&\n !node.computed &&\n node.property.type === AST_NODE_TYPES.Identifier &&\n isSimpleReference(node.object));\n\nconst rootIdentifierName = (node: TSESTree.Node): string | undefined =>\n node.type === AST_NODE_TYPES.Identifier\n ? node.name\n : node.type === AST_NODE_TYPES.MemberExpression\n ? rootIdentifierName(node.object)\n : undefined;\n\ntype ComparisonOperator = '!==' | '<' | '<=' | '>' | '>=';\n\nconst COMPARISON_OPERATORS: ReadonlySet<string> = new Set([\n '!==',\n '<',\n '<=',\n '>',\n '>=',\n] as const satisfies readonly ComparisonOperator[]);\n\n/** Mirror of each comparison when its operands are swapped. */\nconst FLIPPED_OPERATORS = {\n '!==': '!==',\n '<': '>',\n '<=': '>=',\n '>': '<',\n '>=': '<=',\n} as const satisfies ReadonlyRecord<ComparisonOperator, ComparisonOperator>;\n\n/** Classifies `index OP N` (a comparison against an integer literal). */\nconst classifyLiteralComparison = (\n op: ComparisonOperator,\n n: number,\n): CopyOp | undefined => {\n switch (op) {\n case '>=':\n return normalizeCountedOp('skip', n);\n\n case '>':\n return normalizeCountedOp('skip', n + 1);\n\n case '!==':\n return n === 0 ? { fn: 'tail' } : undefined;\n\n case '<':\n return normalizeCountedOp('take', n);\n\n case '<=':\n return normalizeCountedOp('take', n + 1);\n }\n};\n\n/** Classifies `index OP <xs>.length - M`. */\nconst classifyLengthComparison = (\n op: ComparisonOperator,\n m: number,\n): CopyOp | undefined => {\n switch (op) {\n case '<':\n return normalizeCountedOp('skipLast', m);\n\n case '<=':\n return normalizeCountedOp('skipLast', m - 1);\n\n case '!==':\n return m === 1 ? { fn: 'butLast' } : undefined;\n\n case '>=':\n return normalizeCountedOp('takeLast', m);\n\n case '>':\n return normalizeCountedOp('takeLast', m - 1);\n }\n};\n\nexport const preferCanonicalArraySlicing: TSESLint.RuleModule<\n MessageIds,\n Options\n> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Unify non-mutating array element addition/removal patterns (`slice` / `toSpliced` / `filter` / `concat` / spread) into the corresponding ts-data-forge functions: `Arr.tail`, `Arr.skip`, `Arr.take`, `Arr.butLast`, `Arr.skipLast`, `Arr.takeLast`, `Arr.toUnshifted`, `Arr.toPushed`.',\n },\n fixable: 'code',\n schema: [],\n messages: {\n preferCanonicalArraySlicing:\n 'Replace with `{{replacement}}` from ts-data-forge.',\n },\n },\n\n create: (context) => {\n const sourceCode = context.sourceCode;\n\n const program = sourceCode.ast;\n\n const tsDataForgeImport = getTsDataForgeImport(program);\n\n const parserServices = ESLintUtils.getParserServices(context);\n\n const checker = parserServices.program.getTypeChecker();\n\n /**\n * The in-scope way to reference the `Arr` namespace: a named import\n * (possibly aliased), a namespace import member, or the canonical `Arr`\n * (added by the import fix when nothing is imported yet).\n */\n const arrLocalName = getImportedLocalName(tsDataForgeImport, 'Arr');\n\n const namespaceLocalName = tsDataForgeImport?.specifiers.find(\n (specifier) => specifier.type === AST_NODE_TYPES.ImportNamespaceSpecifier,\n )?.local.name;\n\n const arrRef =\n arrLocalName ??\n (namespaceLocalName === undefined ? 'Arr' : `${namespaceLocalName}.Arr`);\n\n const needsArrImport =\n arrLocalName === undefined && namespaceLocalName === undefined;\n\n /**\n * Deferred (non-concrete) types are reduced to their base constraint\n * before checking; `any` / `unknown` are conservatively not array-like.\n */\n const isArrayOrTupleType = (type: ts.Type): boolean => {\n if ((type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) {\n return false;\n }\n\n if (type.isUnion()) {\n return type.types.every(isArrayOrTupleType);\n }\n\n if (\n (type.flags &\n (ts.TypeFlags.TypeParameter |\n ts.TypeFlags.IndexedAccess |\n ts.TypeFlags.Conditional |\n ts.TypeFlags.Substitution)) !==\n 0\n ) {\n const constraint = checker.getBaseConstraintOfType(type);\n\n return constraint !== undefined && isArrayOrTupleType(constraint);\n }\n\n return checker.isArrayType(type) || checker.isTupleType(type);\n };\n\n const getTypeOf = (node: TSESTree.Node): ts.Type =>\n checker.getTypeAtLocation(parserServices.esTreeNodeToTSNodeMap.get(node));\n\n /**\n * `true` only when the type can never be an array in any branch, so\n * `xs.concat(v)` is guaranteed to append `v` as one element rather than\n * flattening it.\n */\n const isDefinitelyNotArrayType = (type: ts.Type): boolean => {\n if ((type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) {\n return false;\n }\n\n if (type.isUnion()) {\n return type.types.every(isDefinitelyNotArrayType);\n }\n\n if (\n (type.flags &\n (ts.TypeFlags.TypeParameter |\n ts.TypeFlags.IndexedAccess |\n ts.TypeFlags.Conditional |\n ts.TypeFlags.Substitution)) !==\n 0\n ) {\n const constraint = checker.getBaseConstraintOfType(type);\n\n return constraint !== undefined && isDefinitelyNotArrayType(constraint);\n }\n\n return !checker.isArrayType(type) && !checker.isTupleType(type);\n };\n\n /** Argument-position text; a comma (sequence) expression needs parens. */\n const toArgText = (node: TSESTree.Node): string => {\n const text = sourceCode.getText(node);\n\n return node.type === AST_NODE_TYPES.SequenceExpression\n ? `(${text})`\n : text;\n };\n\n const toReplacementText = (op: CopyOp, xsText: string): string => {\n switch (op.fn) {\n case 'tail':\n case 'butLast':\n return `${arrRef}.${op.fn}(${xsText})`;\n\n case 'skip':\n case 'take':\n case 'skipLast':\n case 'takeLast':\n return `${arrRef}.${op.fn}(${xsText}, ${op.count})`;\n\n // Emit the curried form for `toUnshifted` so the new element stays in\n // the same textual position as in the `[item, ...xs]` source, keeping\n // the rewrite easy to read.\n case 'toUnshifted':\n return `${arrRef}.toUnshifted(${op.itemText})(${xsText})`;\n\n case 'toPushed':\n return `${arrRef}.toPushed(${xsText}, ${op.itemText})`;\n }\n };\n\n const mut_nodesToFix: {\n node: TSESTree.Node;\n replacement: string;\n }[] = [];\n\n const report = (node: TSESTree.Node, op: CopyOp, xsText: string): void => {\n mut_nodesToFix.push({\n node,\n replacement: toReplacementText(op, xsText),\n });\n };\n\n /** Matches `<xsText>.length` (non-computed). */\n const isReceiverLengthAccess = (\n node: TSESTree.Node,\n receiverText: string,\n ): boolean =>\n node.type === AST_NODE_TYPES.MemberExpression &&\n !node.computed &&\n node.property.type === AST_NODE_TYPES.Identifier &&\n node.property.name === 'length' &&\n sourceCode.getText(node.object) === receiverText;\n\n /** Matches `<xsText>.length - M` (M ≥ 1) and returns `M`. */\n const matchLengthMinus = (\n node: TSESTree.Node,\n receiverText: string,\n ): number | undefined => {\n if (\n node.type !== AST_NODE_TYPES.BinaryExpression ||\n node.operator !== '-' ||\n !isReceiverLengthAccess(node.left, receiverText)\n ) {\n return undefined;\n }\n\n const m = asNonNegativeIntegerLiteral(node.right);\n\n return m !== undefined && m >= 1 ? m : undefined;\n };\n\n /**\n * Matches an index-only filter callback (`(_, i) => <comparison>`) and\n * classifies the comparison. Both a leading index (`i >= 1`) and a\n * trailing index (`1 <= i`) operand order are accepted.\n */\n const classifyFilterCallback = (\n callback: TSESTree.CallExpressionArgument,\n receiver: TSESTree.Expression,\n receiverText: string,\n ): CopyOp | undefined => {\n if (\n callback.type !== AST_NODE_TYPES.ArrowFunctionExpression ||\n callback.async ||\n callback.body.type === AST_NODE_TYPES.BlockStatement ||\n callback.params.length !== 2\n ) {\n return undefined;\n }\n\n const [elementParam, indexParam] = callback.params;\n\n if (\n elementParam === undefined ||\n indexParam === undefined ||\n elementParam.type !== AST_NODE_TYPES.Identifier ||\n indexParam.type !== AST_NODE_TYPES.Identifier\n ) {\n return undefined;\n }\n\n // A callback parameter shadowing the receiver's root binding (e.g.\n // `xs.filter((xs, i) => i < xs.length - 1)`) would make the textual\n // `xs.length` comparison refer to the element, not the receiver.\n const receiverRoot = rootIdentifierName(receiver);\n\n if (\n receiverRoot !== undefined &&\n (elementParam.name === receiverRoot || indexParam.name === receiverRoot)\n ) {\n return undefined;\n }\n\n const body = callback.body;\n\n if (\n body.type !== AST_NODE_TYPES.BinaryExpression ||\n !COMPARISON_OPERATORS.has(body.operator)\n ) {\n return undefined;\n }\n\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n const rawOperator = body.operator as ComparisonOperator;\n\n const isIndex = (side: TSESTree.Node): boolean =>\n side.type === AST_NODE_TYPES.Identifier &&\n side.name === indexParam.name;\n\n const [op, other] = isIndex(body.left)\n ? ([rawOperator, body.right] as const)\n : isIndex(body.right)\n ? ([FLIPPED_OPERATORS[rawOperator], body.left] as const)\n : [undefined, undefined];\n\n // `op` and `other` are set together (both from the same tuple), so\n // checking `op` also guarantees `other` is defined.\n if (op === undefined || isIndex(other)) {\n return undefined;\n }\n\n // The element parameter must not participate in the comparison (only\n // the index side and a receiver-independent bound are recognized, so\n // reaching here with an element reference is impossible for literals,\n // but `xs.length - N` needs a stable receiver).\n const n = asNonNegativeIntegerLiteral(other);\n\n if (n !== undefined) {\n return classifyLiteralComparison(op, n);\n }\n\n if (!isSimpleReference(receiver)) return undefined;\n\n const m = matchLengthMinus(other, receiverText);\n\n return m === undefined ? undefined : classifyLengthComparison(op, m);\n };\n\n const classifySliceArgs = (\n args: readonly TSESTree.CallExpressionArgument[],\n receiver: TSESTree.Expression,\n ): CopyOp | undefined => {\n if (args.length === 1) {\n const [start] = args;\n\n if (start === undefined) return undefined;\n\n const n = asNonNegativeIntegerLiteral(start);\n\n if (n !== undefined) return normalizeCountedOp('skip', n);\n\n const negated = asNegatedPositiveIntegerLiteral(start);\n\n return negated === undefined\n ? undefined\n : normalizeCountedOp('takeLast', negated);\n }\n\n if (args.length === 2) {\n const [start, end] = args;\n\n if (\n start === undefined ||\n end === undefined ||\n asNonNegativeIntegerLiteral(start) !== 0\n ) {\n return undefined;\n }\n\n const n = asNonNegativeIntegerLiteral(end);\n\n if (n !== undefined) return normalizeCountedOp('take', n);\n\n const negated = asNegatedPositiveIntegerLiteral(end);\n\n if (negated !== undefined) {\n return normalizeCountedOp('skipLast', negated);\n }\n\n if (!isSimpleReference(receiver)) return undefined;\n\n const m = matchLengthMinus(end, sourceCode.getText(receiver));\n\n return m === undefined ? undefined : normalizeCountedOp('skipLast', m);\n }\n\n return undefined;\n };\n\n const classifyToSplicedArgs = (\n args: readonly TSESTree.CallExpressionArgument[],\n receiver: TSESTree.Expression,\n receiverText: string,\n ): CopyOp | undefined => {\n if (args.length === 1) {\n const [start] = args;\n\n if (start === undefined) return undefined;\n\n const n = asNonNegativeIntegerLiteral(start);\n\n // `xs.toSpliced(N)` removes everything from index N on → take N.\n if (n !== undefined) return normalizeCountedOp('take', n);\n\n const negated = asNegatedPositiveIntegerLiteral(start);\n\n // `xs.toSpliced(-N)` removes the last N elements → skipLast N.\n return negated === undefined\n ? undefined\n : normalizeCountedOp('skipLast', negated);\n }\n\n if (args.length === 2) {\n const [start, deleteCount] = args;\n\n if (\n start === undefined ||\n deleteCount === undefined ||\n asNonNegativeIntegerLiteral(start) !== 0\n ) {\n return undefined;\n }\n\n // `xs.toSpliced(0, N)` removes the first N elements → skip N.\n const n = asNonNegativeIntegerLiteral(deleteCount);\n\n if (n !== undefined) return normalizeCountedOp('skip', n);\n\n // `xs.toSpliced(0, xs.length - N)` keeps the last N → takeLast N.\n // (Note: `xs.toSpliced(0, -N)` clamps the delete count to 0 and is a\n // plain copy, so it is deliberately NOT treated as takeLast.)\n if (!isSimpleReference(receiver)) return undefined;\n\n const m = matchLengthMinus(deleteCount, receiverText);\n\n return m === undefined ? undefined : normalizeCountedOp('takeLast', m);\n }\n\n if (args.length === 3) {\n const [start, deleteCount, item] = args;\n\n if (\n start === undefined ||\n deleteCount === undefined ||\n item === undefined ||\n item.type === AST_NODE_TYPES.SpreadElement ||\n asNonNegativeIntegerLiteral(deleteCount) !== 0\n ) {\n return undefined;\n }\n\n // `xs.toSpliced(0, 0, item)` prepends → toUnshifted.\n if (asNonNegativeIntegerLiteral(start) === 0) {\n return { fn: 'toUnshifted', itemText: toArgText(item) };\n }\n\n // `xs.toSpliced(xs.length, 0, item)` appends → toPushed.\n if (\n isSimpleReference(receiver) &&\n isReceiverLengthAccess(start, receiverText)\n ) {\n return { fn: 'toPushed', itemText: toArgText(item) };\n }\n\n return undefined;\n }\n\n return undefined;\n };\n\n return {\n CallExpression: (node) => {\n const callee = node.callee;\n\n if (\n callee.type !== AST_NODE_TYPES.MemberExpression ||\n callee.computed ||\n callee.property.type !== AST_NODE_TYPES.Identifier\n ) {\n return;\n }\n\n const methodName = callee.property.name;\n\n const receiver = callee.object;\n\n // `Arr.skip(xs, 1)` → `Arr.tail(xs)`, `Arr.skipLast(xs, 1)` →\n // `Arr.butLast(xs)` (also via a namespace import or an alias).\n if (methodName === 'skip' || methodName === 'skipLast') {\n const isArrObject =\n (receiver.type === AST_NODE_TYPES.Identifier &&\n receiver.name === arrLocalName) ||\n (receiver.type === AST_NODE_TYPES.MemberExpression &&\n !receiver.computed &&\n receiver.object.type === AST_NODE_TYPES.Identifier &&\n receiver.object.name === namespaceLocalName &&\n receiver.property.type === AST_NODE_TYPES.Identifier &&\n receiver.property.name === 'Arr');\n\n const [arrayArg, countArg] = node.arguments;\n\n if (\n isArrObject &&\n arrayArg !== undefined &&\n countArg !== undefined &&\n node.arguments.length === 2 &&\n arrayArg.type !== AST_NODE_TYPES.SpreadElement &&\n asNonNegativeIntegerLiteral(countArg) === 1\n ) {\n report(\n node,\n { fn: methodName === 'skip' ? 'tail' : 'butLast' },\n toArgText(arrayArg),\n );\n }\n\n return;\n }\n\n // `[item].concat(xs)` → `Arr.toUnshifted(xs, item)`.\n if (\n methodName === 'concat' &&\n receiver.type === AST_NODE_TYPES.ArrayExpression\n ) {\n const [element] = receiver.elements;\n\n const [arg] = node.arguments;\n\n if (\n element !== null &&\n element !== undefined &&\n arg !== undefined &&\n receiver.elements.length === 1 &&\n element.type !== AST_NODE_TYPES.SpreadElement &&\n node.arguments.length === 1 &&\n arg.type !== AST_NODE_TYPES.SpreadElement &&\n isArrayOrTupleType(getTypeOf(arg))\n ) {\n report(\n node,\n { fn: 'toUnshifted', itemText: toArgText(element) },\n toArgText(arg),\n );\n }\n\n return;\n }\n\n if (\n methodName !== 'slice' &&\n methodName !== 'toSpliced' &&\n methodName !== 'filter' &&\n methodName !== 'concat'\n ) {\n return;\n }\n\n // The receiver must be an array or tuple (not a string / typed array\n // / other object that happens to share the method name).\n if (!isArrayOrTupleType(getTypeOf(receiver))) return;\n\n const receiverText = sourceCode.getText(receiver);\n\n switch (methodName) {\n case 'slice': {\n const op = classifySliceArgs(node.arguments, receiver);\n\n if (op !== undefined) report(node, op, toArgText(receiver));\n\n return;\n }\n\n case 'toSpliced': {\n const op = classifyToSplicedArgs(\n node.arguments,\n receiver,\n receiverText,\n );\n\n if (op !== undefined) report(node, op, toArgText(receiver));\n\n return;\n }\n\n case 'filter': {\n const [callback] = node.arguments;\n\n if (callback === undefined || node.arguments.length !== 1) {\n return;\n }\n\n const op = classifyFilterCallback(callback, receiver, receiverText);\n\n if (op !== undefined) report(node, op, toArgText(receiver));\n\n return;\n }\n\n case 'concat': {\n const [arg] = node.arguments;\n\n if (\n arg === undefined ||\n node.arguments.length !== 1 ||\n arg.type === AST_NODE_TYPES.SpreadElement\n ) {\n return;\n }\n\n // `xs.concat([item])` → `Arr.toPushed(xs, item)`.\n if (arg.type === AST_NODE_TYPES.ArrayExpression) {\n const [element] = arg.elements;\n\n if (\n element !== null &&\n element !== undefined &&\n arg.elements.length === 1 &&\n element.type !== AST_NODE_TYPES.SpreadElement\n ) {\n report(\n node,\n { fn: 'toPushed', itemText: toArgText(element) },\n toArgText(receiver),\n );\n }\n\n return;\n }\n\n // `xs.concat(item)` appends only when `item` can never be an\n // array (otherwise `concat` would flatten it).\n if (isDefinitelyNotArrayType(getTypeOf(arg))) {\n report(\n node,\n { fn: 'toPushed', itemText: toArgText(arg) },\n toArgText(receiver),\n );\n }\n }\n }\n },\n\n ArrayExpression: (node) => {\n // `[item, ...xs]` → `Arr.toUnshifted(xs, item)`,\n // `[...xs, item]` → `Arr.toPushed(xs, item)`.\n if (node.elements.length !== 2) return;\n\n const [first, second] = node.elements;\n\n if (\n first === null ||\n first === undefined ||\n second === null ||\n second === undefined\n ) {\n return;\n }\n\n const [spread, item, fn] =\n first.type === AST_NODE_TYPES.SpreadElement &&\n second.type !== AST_NODE_TYPES.SpreadElement\n ? ([first, second, 'toPushed'] as const)\n : first.type !== AST_NODE_TYPES.SpreadElement &&\n second.type === AST_NODE_TYPES.SpreadElement\n ? ([second, first, 'toUnshifted'] as const)\n : [undefined, undefined, undefined];\n\n // `spread`, `item`, and `fn` are set together (all from the same\n // tuple), so checking `spread` also guarantees the others are\n // defined.\n if (spread === undefined) return;\n\n // Spreading a non-array iterable (`[v, ...someSet]`) is a genuine\n // conversion, not an element addition to an array.\n if (!isArrayOrTupleType(getTypeOf(spread.argument))) return;\n\n report(\n node,\n { fn, itemText: toArgText(item) },\n toArgText(spread.argument),\n );\n },\n\n 'Program:exit': () => {\n for (const [index, nodeInfo] of mut_nodesToFix.entries()) {\n context.report({\n node: nodeInfo.node,\n messageId: 'preferCanonicalArraySlicing',\n data: { replacement: nodeInfo.replacement },\n fix: (fixer) => {\n const importFixes =\n index === 0 && needsArrImport\n ? buildImportFixes(fixer, program, tsDataForgeImport, ['Arr'])\n : [];\n\n return [\n ...importFixes,\n fixer.replaceText(nodeInfo.node, nodeInfo.replacement),\n ];\n },\n });\n }\n },\n };\n },\n defaultOptions: [],\n} as const;\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-canonical-length-guard.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-canonical-length-guard.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"prefer-canonical-length-guard.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-canonical-length-guard.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;AASlC,KAAK,OAAO,GAAG,SAAS,EAAE,CAAC;AAE3B,KAAK,UAAU,GACX,mBAAmB,GACnB,yBAAyB,GACzB,uBAAuB,GACvB,qBAAqB,GACrB,qBAAqB,GACrB,eAAe,CAAC;AA8EpB,eAAO,MAAM,0BAA0B,EAAE,QAAQ,CAAC,UAAU,CAC1D,UAAU,EACV,OAAO,CAsEC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-canonical-length-guard.mjs","sources":["../../src/rules/prefer-canonical-length-guard.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { getImportedLocalName, getTsDataForgeImport } from './import-utils.mjs';\nimport { preferArrIsBoundedLengthArray } from './prefer-arr-is-bounded-length-array.mjs';\nimport { preferArrIsFixedLengthArray } from './prefer-arr-is-fixed-length-array.mjs';\nimport { preferArrIsMaxLengthArray } from './prefer-arr-is-max-length-array.mjs';\nimport { preferArrIsMinLengthArray } from './prefer-arr-is-min-length-array.mjs';\nimport { preferArrIsNonEmpty } from './prefer-arr-is-non-empty.mjs';\n\ntype Options = readonly [];\n\ntype MessageIds =\n | 'useCanonicalGuard'\n | 'useIsBoundedLengthArray'\n | 'useIsFixedLengthArray'\n | 'useIsMaxLengthArray'\n | 'useIsMinLengthArray'\n | 'useIsNonEmpty';\n\n/**\n * The `xs.length <op> n` → `Arr.is*` rules folded into this one.\n *\n * Their implementations are reused as-is rather than rewritten: this rule\n * merges their visitors, so every case they already cover (type-aware array\n * checks, `Arr` import insertion, the `&&`-bounded-range special case) keeps\n * behaving exactly as before, and their co-located tests keep exercising them\n * directly.\n */\nconst COMPARISON_RULES = [\n preferArrIsNonEmpty,\n preferArrIsMinLengthArray,\n preferArrIsMaxLengthArray,\n preferArrIsBoundedLengthArray,\n preferArrIsFixedLengthArray,\n] as const;\n\nconst MESSAGES = {\n useCanonicalGuard:\n 'Replace `{{arrName}}.{{guard}}({{boundsText}})` with `{{arrName}}.{{replacement}}(...)`: the bound makes the two guards equivalent.',\n useIsNonEmpty: preferArrIsNonEmpty.meta.messages.useIsNonEmpty,\n useIsMinLengthArray:\n preferArrIsMinLengthArray.meta.messages.useIsMinLengthArray,\n useIsMaxLengthArray:\n preferArrIsMaxLengthArray.meta.messages.useIsMaxLengthArray,\n useIsBoundedLengthArray:\n preferArrIsBoundedLengthArray.meta.messages.useIsBoundedLengthArray,\n useIsFixedLengthArray:\n preferArrIsFixedLengthArray.meta.messages.useIsFixedLengthArray,\n} as const satisfies Record<MessageIds, string>;\n\n/**\n * Length guards whose bound makes them redundant with a named degenerate\n * guard, keyed by guard name.\n *\n * Every entry stays inside its own family rather than crossing the\n * branded/structural divide:\n *\n * - the branded `*Array` guards go to `isEmpty` / `isNonEmpty`, which narrow to\n * `FixedLengthArray<0, E> & Xs` / `MinLengthArray<1, E> & Xs`;\n * - the structural `*Tuple` guards go to `isEmptyTuple` / `isNonEmptyTuple`,\n * which narrow to `readonly []` / `MinLengthTuple<1, E>`.\n *\n * Most entries are then **type-identical** — `isFixedLengthArray(0, xs)` and\n * `isMinLengthArray(1, xs)` produce exactly what `isEmpty` / `isNonEmpty`\n * narrow to, and `FixedLengthTuple<0, E>`, `MaxLengthTuple<0, E>` and\n * `BoundedLengthTuple<0, 0, E>` are all `readonly []`. Two *strengthen*:\n * `MaxLengthArray<0, E>` and `BoundedLengthArray<0, 0, E>` do not include the\n * `readonly []` structural part that `FixedLengthArray<0, E>` intersects on, so\n * rewriting them to `isEmpty` adds it. That is sound for a guard — the narrowed\n * type only flows outwards, and the stronger type is assignable everywhere the\n * old one was — and it stays within the branded family.\n *\n * Sending a `*Tuple` guard to the branded `isEmpty` would also be sound, but it\n * would silently change which family the value belongs to, which is why each\n * family keeps its own lane.\n */\nconst GUARD_REWRITES = [\n { guard: 'isFixedLengthArray', bounds: [0], replacement: 'isEmpty' },\n { guard: 'isMaxLengthArray', bounds: [0], replacement: 'isEmpty' },\n { guard: 'isBoundedLengthArray', bounds: [0, 0], replacement: 'isEmpty' },\n { guard: 'isMinLengthArray', bounds: [1], replacement: 'isNonEmpty' },\n { guard: 'isFixedLengthTuple', bounds: [0], replacement: 'isEmptyTuple' },\n { guard: 'isMaxLengthTuple', bounds: [0], replacement: 'isEmptyTuple' },\n {\n guard: 'isBoundedLengthTuple',\n bounds: [0, 0],\n replacement: 'isEmptyTuple',\n },\n { guard: 'isMinLengthTuple', bounds: [1], replacement: 'isNonEmptyTuple' },\n] as const satisfies readonly Readonly<{\n guard: string;\n bounds: readonly number[];\n replacement: string;\n}>[];\n\nexport const preferCanonicalLengthGuard: TSESLint.RuleModule<\n MessageIds,\n Options\n> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Normalize array-length checks to their canonical `Arr` guard: `xs.length <op> n` becomes the matching `Arr.is*` guard, and a degenerate guard becomes the named one for its family (`Arr.isFixedLengthArray(0, xs)` → `Arr.isEmpty`, `Arr.isFixedLengthTuple(0, xs)` → `Arr.isEmptyTuple`).',\n },\n fixable: 'code',\n schema: [],\n messages: MESSAGES,\n },\n\n create: (context) => {\n const sourceCode = context.sourceCode;\n\n const arrLocalName = getImportedLocalName(\n getTsDataForgeImport(sourceCode.ast),\n 'Arr',\n );\n\n const comparisonVisitors = COMPARISON_RULES.map((rule) =>\n rule.create(context),\n );\n\n if (arrLocalName === undefined) return mergeVisitors(comparisonVisitors);\n\n return mergeVisitors([\n ...comparisonVisitors,\n {\n CallExpression: (node) => {\n const guardName = getGuardName(node, arrLocalName);\n\n if (guardName === undefined) return;\n\n const rewrite = GUARD_REWRITES.find(\n (entry) => entry.guard === guardName,\n );\n\n if (rewrite === undefined || !matchesBounds(node, rewrite.bounds)) {\n return;\n }\n\n // The array is the last argument; the bounds precede it.\n const array = node.arguments[rewrite.bounds.length];\n\n if (array === undefined) return;\n\n const arrayText = sourceCode.getText(array);\n\n context.report({\n node,\n messageId: 'useCanonicalGuard',\n data: {\n arrName: arrLocalName,\n guard: guardName,\n boundsText: [...rewrite.bounds.map(String), '…'].join(', '),\n replacement: rewrite.replacement,\n },\n fix: (fixer) =>\n fixer.replaceText(\n node,\n `${arrLocalName}.${rewrite.replacement}(${arrayText})`,\n ),\n });\n },\n },\n ]);\n },\n defaultOptions: [],\n} as const;\n\n/**\n * Combines several rule listeners into one, calling every handler registered\n * for a given selector in order.\n */\nconst mergeVisitors = (\n // `RuleListener` holds mutable AST handler signatures, so it is not deeply\n // readonly.\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n visitors: readonly TSESLint.RuleListener[],\n): TSESLint.RuleListener => {\n const mut_merged: Record<string, ((node: never) => void)[]> = {};\n\n for (const visitor of visitors) {\n for (const [selector, handler] of Object.entries(visitor)) {\n if (typeof handler !== 'function') continue;\n\n const mut_handlers = mut_merged[selector] ?? [];\n\n mut_handlers.push(handler);\n\n mut_merged[selector] = mut_handlers;\n }\n }\n\n return Object.fromEntries(\n Object.entries(mut_merged).map(([selector, handlers]) => [\n selector,\n (node: never) => {\n for (const handler of handlers) {\n handler(node);\n }\n },\n ]),\n );\n};\n\n/** The `Arr.<guard>` method name of `node`, or `undefined` for other calls. */\nconst getGuardName = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n node: TSESTree.CallExpression,\n arrLocalName: string,\n): string | undefined => {\n const { callee } = node;\n\n return callee.type === AST_NODE_TYPES.MemberExpression &&\n !callee.computed &&\n callee.object.type === AST_NODE_TYPES.Identifier &&\n callee.object.name === arrLocalName &&\n callee.property.type === AST_NODE_TYPES.Identifier\n ? callee.property.name\n : undefined;\n};\n\n/**\n * Whether the call is `guard(...bounds, array)` with every bound written as the\n * exact numeric literal the rewrite requires.\n *\n * The arity check also excludes the curried form (`guard(...bounds)`): the\n * degenerate guards take only the array, so there is nothing to rewrite it to.\n */\nconst matchesBounds = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n node: TSESTree.CallExpression,\n bounds: readonly number[],\n): boolean => {\n const args = node.arguments;\n\n if (args.length !== bounds.length + 1 || node.typeArguments !== undefined) {\n return false;\n }\n\n return args.every(\n (arg, index) =>\n arg.type !== AST_NODE_TYPES.SpreadElement &&\n (index === bounds.length ||\n (arg.type === AST_NODE_TYPES.Literal && arg.value === bounds[index])),\n );\n};\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"prefer-canonical-length-guard.mjs","sources":["../../src/rules/prefer-canonical-length-guard.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { type MutableRecord, type ReadonlyRecord } from 'ts-type-forge';\nimport { getImportedLocalName, getTsDataForgeImport } from './import-utils.mjs';\nimport { preferArrIsBoundedLengthArray } from './prefer-arr-is-bounded-length-array.mjs';\nimport { preferArrIsFixedLengthArray } from './prefer-arr-is-fixed-length-array.mjs';\nimport { preferArrIsMaxLengthArray } from './prefer-arr-is-max-length-array.mjs';\nimport { preferArrIsMinLengthArray } from './prefer-arr-is-min-length-array.mjs';\nimport { preferArrIsNonEmpty } from './prefer-arr-is-non-empty.mjs';\n\ntype Options = readonly [];\n\ntype MessageIds =\n | 'useCanonicalGuard'\n | 'useIsBoundedLengthArray'\n | 'useIsFixedLengthArray'\n | 'useIsMaxLengthArray'\n | 'useIsMinLengthArray'\n | 'useIsNonEmpty';\n\n/**\n * The `xs.length <op> n` → `Arr.is*` rules folded into this one.\n *\n * Their implementations are reused as-is rather than rewritten: this rule\n * merges their visitors, so every case they already cover (type-aware array\n * checks, `Arr` import insertion, the `&&`-bounded-range special case) keeps\n * behaving exactly as before, and their co-located tests keep exercising them\n * directly.\n */\nconst COMPARISON_RULES = [\n preferArrIsNonEmpty,\n preferArrIsMinLengthArray,\n preferArrIsMaxLengthArray,\n preferArrIsBoundedLengthArray,\n preferArrIsFixedLengthArray,\n] as const;\n\nconst MESSAGES = {\n useCanonicalGuard:\n 'Replace `{{arrName}}.{{guard}}({{boundsText}})` with `{{arrName}}.{{replacement}}(...)`: the bound makes the two guards equivalent.',\n useIsNonEmpty: preferArrIsNonEmpty.meta.messages.useIsNonEmpty,\n useIsMinLengthArray:\n preferArrIsMinLengthArray.meta.messages.useIsMinLengthArray,\n useIsMaxLengthArray:\n preferArrIsMaxLengthArray.meta.messages.useIsMaxLengthArray,\n useIsBoundedLengthArray:\n preferArrIsBoundedLengthArray.meta.messages.useIsBoundedLengthArray,\n useIsFixedLengthArray:\n preferArrIsFixedLengthArray.meta.messages.useIsFixedLengthArray,\n} as const satisfies ReadonlyRecord<MessageIds, string>;\n\n/**\n * Length guards whose bound makes them redundant with a named degenerate\n * guard, keyed by guard name.\n *\n * Every entry stays inside its own family rather than crossing the\n * branded/structural divide:\n *\n * - the branded `*Array` guards go to `isEmpty` / `isNonEmpty`, which narrow to\n * `FixedLengthArray<0, E> & Xs` / `MinLengthArray<1, E> & Xs`;\n * - the structural `*Tuple` guards go to `isEmptyTuple` / `isNonEmptyTuple`,\n * which narrow to `readonly []` / `MinLengthTuple<1, E>`.\n *\n * Most entries are then **type-identical** — `isFixedLengthArray(0, xs)` and\n * `isMinLengthArray(1, xs)` produce exactly what `isEmpty` / `isNonEmpty`\n * narrow to, and `FixedLengthTuple<0, E>`, `MaxLengthTuple<0, E>` and\n * `BoundedLengthTuple<0, 0, E>` are all `readonly []`. Two *strengthen*:\n * `MaxLengthArray<0, E>` and `BoundedLengthArray<0, 0, E>` do not include the\n * `readonly []` structural part that `FixedLengthArray<0, E>` intersects on, so\n * rewriting them to `isEmpty` adds it. That is sound for a guard — the narrowed\n * type only flows outwards, and the stronger type is assignable everywhere the\n * old one was — and it stays within the branded family.\n *\n * Sending a `*Tuple` guard to the branded `isEmpty` would also be sound, but it\n * would silently change which family the value belongs to, which is why each\n * family keeps its own lane.\n */\nconst GUARD_REWRITES = [\n { guard: 'isFixedLengthArray', bounds: [0], replacement: 'isEmpty' },\n { guard: 'isMaxLengthArray', bounds: [0], replacement: 'isEmpty' },\n { guard: 'isBoundedLengthArray', bounds: [0, 0], replacement: 'isEmpty' },\n { guard: 'isMinLengthArray', bounds: [1], replacement: 'isNonEmpty' },\n { guard: 'isFixedLengthTuple', bounds: [0], replacement: 'isEmptyTuple' },\n { guard: 'isMaxLengthTuple', bounds: [0], replacement: 'isEmptyTuple' },\n {\n guard: 'isBoundedLengthTuple',\n bounds: [0, 0],\n replacement: 'isEmptyTuple',\n },\n { guard: 'isMinLengthTuple', bounds: [1], replacement: 'isNonEmptyTuple' },\n] as const satisfies readonly Readonly<{\n guard: string;\n bounds: readonly number[];\n replacement: string;\n}>[];\n\nexport const preferCanonicalLengthGuard: TSESLint.RuleModule<\n MessageIds,\n Options\n> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Normalize array-length checks to their canonical `Arr` guard: `xs.length <op> n` becomes the matching `Arr.is*` guard, and a degenerate guard becomes the named one for its family (`Arr.isFixedLengthArray(0, xs)` → `Arr.isEmpty`, `Arr.isFixedLengthTuple(0, xs)` → `Arr.isEmptyTuple`).',\n },\n fixable: 'code',\n schema: [],\n messages: MESSAGES,\n },\n\n create: (context) => {\n const sourceCode = context.sourceCode;\n\n const arrLocalName = getImportedLocalName(\n getTsDataForgeImport(sourceCode.ast),\n 'Arr',\n );\n\n const comparisonVisitors = COMPARISON_RULES.map((rule) =>\n rule.create(context),\n );\n\n if (arrLocalName === undefined) return mergeVisitors(comparisonVisitors);\n\n return mergeVisitors([\n ...comparisonVisitors,\n {\n CallExpression: (node) => {\n const guardName = getGuardName(node, arrLocalName);\n\n if (guardName === undefined) return;\n\n const rewrite = GUARD_REWRITES.find(\n (entry) => entry.guard === guardName,\n );\n\n if (rewrite === undefined || !matchesBounds(node, rewrite.bounds)) {\n return;\n }\n\n // The array is the last argument; the bounds precede it.\n const array = node.arguments[rewrite.bounds.length];\n\n if (array === undefined) return;\n\n const arrayText = sourceCode.getText(array);\n\n context.report({\n node,\n messageId: 'useCanonicalGuard',\n data: {\n arrName: arrLocalName,\n guard: guardName,\n boundsText: [...rewrite.bounds.map(String), '…'].join(', '),\n replacement: rewrite.replacement,\n },\n fix: (fixer) =>\n fixer.replaceText(\n node,\n `${arrLocalName}.${rewrite.replacement}(${arrayText})`,\n ),\n });\n },\n },\n ]);\n },\n defaultOptions: [],\n} as const;\n\n/**\n * Combines several rule listeners into one, calling every handler registered\n * for a given selector in order.\n */\nconst mergeVisitors = (\n // `RuleListener` holds mutable AST handler signatures, so it is not deeply\n // readonly.\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n visitors: readonly TSESLint.RuleListener[],\n): TSESLint.RuleListener => {\n const mut_merged: MutableRecord<string, ((node: never) => void)[]> = {};\n\n for (const visitor of visitors) {\n for (const [selector, handler] of Object.entries(visitor)) {\n if (typeof handler !== 'function') continue;\n\n const mut_handlers = mut_merged[selector] ?? [];\n\n mut_handlers.push(handler);\n\n mut_merged[selector] = mut_handlers;\n }\n }\n\n return Object.fromEntries(\n Object.entries(mut_merged).map(([selector, handlers]) => [\n selector,\n (node: never) => {\n for (const handler of handlers) {\n handler(node);\n }\n },\n ]),\n );\n};\n\n/** The `Arr.<guard>` method name of `node`, or `undefined` for other calls. */\nconst getGuardName = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n node: TSESTree.CallExpression,\n arrLocalName: string,\n): string | undefined => {\n const { callee } = node;\n\n return callee.type === AST_NODE_TYPES.MemberExpression &&\n !callee.computed &&\n callee.object.type === AST_NODE_TYPES.Identifier &&\n callee.object.name === arrLocalName &&\n callee.property.type === AST_NODE_TYPES.Identifier\n ? callee.property.name\n : undefined;\n};\n\n/**\n * Whether the call is `guard(...bounds, array)` with every bound written as the\n * exact numeric literal the rewrite requires.\n *\n * The arity check also excludes the curried form (`guard(...bounds)`): the\n * degenerate guards take only the array, so there is nothing to rewrite it to.\n */\nconst matchesBounds = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n node: TSESTree.CallExpression,\n bounds: readonly number[],\n): boolean => {\n const args = node.arguments;\n\n if (args.length !== bounds.length + 1 || node.typeArguments !== undefined) {\n return false;\n }\n\n return args.every(\n (arg, index) =>\n arg.type !== AST_NODE_TYPES.SpreadElement &&\n (index === bounds.length ||\n (arg.type === AST_NODE_TYPES.Literal && arg.value === bounds[index])),\n );\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-comparison-over-nullish-guard.mjs","sources":["../../src/rules/prefer-comparison-over-nullish-guard.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { type DeepReadonly } from 'ts-type-forge';\nimport { buildCalleeResolver, getTsDataForgeImport } from './import-utils.mjs';\n\ntype Options = readonly [];\n\ntype MessageIds = 'preferComparison';\n\ntype ComparisonSpec = Readonly<{\n op: '!==' | '===';\n literal: 'null' | 'undefined';\n}>;\n\n/**\n * The nullish guards that are meant for point-free use (e.g.\n * `xs.filter(isNotUndefined)`). When called with an explicit argument a direct\n * comparison is clearer, so map each to its equivalent operator and literal.\n */\nconst COMPARISON_GUARDS:
|
|
1
|
+
{"version":3,"file":"prefer-comparison-over-nullish-guard.mjs","sources":["../../src/rules/prefer-comparison-over-nullish-guard.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { type DeepReadonly, type ReadonlyRecord } from 'ts-type-forge';\nimport { buildCalleeResolver, getTsDataForgeImport } from './import-utils.mjs';\n\ntype Options = readonly [];\n\ntype MessageIds = 'preferComparison';\n\ntype ComparisonSpec = Readonly<{\n op: '!==' | '===';\n literal: 'null' | 'undefined';\n}>;\n\n/**\n * The nullish guards that are meant for point-free use (e.g.\n * `xs.filter(isNotUndefined)`). When called with an explicit argument a direct\n * comparison is clearer, so map each to its equivalent operator and literal.\n */\nconst COMPARISON_GUARDS: ReadonlyRecord<string, ComparisonSpec> = {\n isUndefined: { op: '===', literal: 'undefined' },\n isNotUndefined: { op: '!==', literal: 'undefined' },\n isNull: { op: '===', literal: 'null' },\n isNotNull: { op: '!==', literal: 'null' },\n} as const;\n\n/**\n * Argument expression kinds whose precedence is lower than equality, so they\n * must be parenthesized when placed on the left of `===` / `!==`.\n */\nconst ARG_NEEDS_PARENS: ReadonlySet<string> = new Set([\n AST_NODE_TYPES.LogicalExpression,\n AST_NODE_TYPES.ConditionalExpression,\n AST_NODE_TYPES.AssignmentExpression,\n AST_NODE_TYPES.SequenceExpression,\n AST_NODE_TYPES.ArrowFunctionExpression,\n AST_NODE_TYPES.YieldExpression,\n]);\n\n/**\n * Returns `true` when the comparison must be wrapped in parentheses to preserve\n * the original grouping given the call expression's parent context.\n */\nconst needsWrappingInParent = (\n node: DeepReadonly<TSESTree.CallExpression>,\n): boolean => {\n const { parent } = node;\n\n // `parent.type` is one of 130+ AST kinds; only a handful need wrapping.\n // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check\n switch (parent.type) {\n case AST_NODE_TYPES.UnaryExpression:\n case AST_NODE_TYPES.AwaitExpression:\n case AST_NODE_TYPES.BinaryExpression:\n case AST_NODE_TYPES.TSNonNullExpression:\n return true;\n\n case AST_NODE_TYPES.MemberExpression:\n return parent.object === node;\n\n case AST_NODE_TYPES.CallExpression:\n case AST_NODE_TYPES.NewExpression:\n return parent.callee === node;\n\n default:\n return false;\n }\n};\n\nexport const preferComparisonOverNullishGuard: TSESLint.RuleModule<\n MessageIds,\n Options\n> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Prefer a direct `=== null` / `!== undefined` comparison over calling `isNull`, `isNotNull`, `isUndefined`, or `isNotUndefined` with an explicit argument (those guards are intended for point-free use such as `xs.filter(isNotUndefined)`).',\n },\n fixable: 'code',\n schema: [],\n messages: {\n preferComparison:\n 'Prefer `{{comparison}}` over calling `{{name}}` with an explicit argument.',\n },\n },\n\n create: (context) => {\n const sourceCode = context.sourceCode;\n\n const resolveGuard = buildCalleeResolver(\n getTsDataForgeImport(sourceCode.ast),\n );\n\n return {\n CallExpression: (node) => {\n if (node.arguments.length !== 1) return;\n\n const argument = node.arguments[0];\n\n if (\n argument === undefined ||\n argument.type === AST_NODE_TYPES.SpreadElement\n ) {\n return;\n }\n\n const resolved = resolveGuard(node.callee);\n\n if (resolved === undefined) return;\n\n const spec = COMPARISON_GUARDS[resolved.canonicalName];\n\n if (spec === undefined) return;\n\n const argRaw = sourceCode.getText(argument);\n\n const argText = ARG_NEEDS_PARENS.has(argument.type)\n ? `(${argRaw})`\n : argRaw;\n\n const comparison = `${argText} ${spec.op} ${spec.literal}`;\n\n const replacement = needsWrappingInParent(node)\n ? `(${comparison})`\n : comparison;\n\n context.report({\n node,\n messageId: 'preferComparison',\n data: { comparison, name: resolved.canonicalName },\n fix: (fixer) => fixer.replaceText(node, replacement),\n });\n },\n };\n },\n defaultOptions: [],\n} as const;\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-ts-data-forge",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "ESLint rules that steer code toward ts-data-forge idioms.",
|
|
6
6
|
"keywords": [
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@typescript-eslint/utils": "^8.65.0",
|
|
38
|
-
"ts-type-forge": "~9.1.
|
|
39
|
-
"ts-data-forge": "14.
|
|
38
|
+
"ts-type-forge": "~9.1.3",
|
|
39
|
+
"ts-data-forge": "14.1.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@typescript-eslint/parser": "8.65.0",
|
package/src/plugin.mts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ReadonlyRecord } from 'ts-type-forge';
|
|
1
2
|
import { tsDataForgeRules } from './rules/index.mjs';
|
|
2
3
|
import { type ESLintFlatConfig, type ESLintPlugin } from './types.mjs';
|
|
3
4
|
|
|
@@ -21,8 +22,9 @@ const recommendedRules = {
|
|
|
21
22
|
'ts-data-forge/prefer-num-safe-parse-float': 'error',
|
|
22
23
|
'ts-data-forge/no-unnecessary-type-guard': 'error',
|
|
23
24
|
'ts-data-forge/prefer-comparison-over-nullish-guard': 'error',
|
|
24
|
-
} as const satisfies
|
|
25
|
-
|
|
25
|
+
} as const satisfies ReadonlyRecord<
|
|
26
|
+
`ts-data-forge/${keyof typeof tsDataForgeRules}`,
|
|
27
|
+
'error'
|
|
26
28
|
>;
|
|
27
29
|
|
|
28
30
|
const recommendedConfig = {
|
|
@@ -111,10 +111,10 @@ export const buildCalleeResolver = (
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
if (
|
|
114
|
+
namespaceName !== undefined &&
|
|
114
115
|
callee.type === AST_NODE_TYPES.MemberExpression &&
|
|
115
116
|
!callee.computed &&
|
|
116
117
|
callee.object.type === AST_NODE_TYPES.Identifier &&
|
|
117
|
-
namespaceName !== undefined &&
|
|
118
118
|
callee.object.name === namespaceName &&
|
|
119
119
|
callee.property.type === AST_NODE_TYPES.Identifier
|
|
120
120
|
) {
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
type TSESLint,
|
|
5
5
|
type TSESTree,
|
|
6
6
|
} from '@typescript-eslint/utils';
|
|
7
|
-
import { type DeepReadonly } from 'ts-type-forge';
|
|
7
|
+
import { type DeepReadonly, type ReadonlyRecord } from 'ts-type-forge';
|
|
8
8
|
import * as ts from 'typescript';
|
|
9
9
|
import {
|
|
10
10
|
buildCalleeResolver,
|
|
@@ -36,12 +36,12 @@ type GuardSpec = DeepReadonly<
|
|
|
36
36
|
| {
|
|
37
37
|
kind: 'narrowTo';
|
|
38
38
|
atoms: AtomKey[];
|
|
39
|
-
replacements?:
|
|
39
|
+
replacements?: ReadonlyRecord<string, string>;
|
|
40
40
|
}
|
|
41
41
|
| {
|
|
42
42
|
kind: 'excludeFrom';
|
|
43
43
|
atoms: AtomKey[];
|
|
44
|
-
replacements?:
|
|
44
|
+
replacements?: ReadonlyRecord<string, string>;
|
|
45
45
|
}
|
|
46
46
|
| { kind: 'nonEmptyString' }
|
|
47
47
|
| { kind: 'record' }
|
|
@@ -63,17 +63,17 @@ const BRAND_MARKER_PROPERTY =
|
|
|
63
63
|
*/
|
|
64
64
|
const MIN_LENGTH_BRAND_KEY = 'MinLength';
|
|
65
65
|
|
|
66
|
-
const NULLISH_REPLACEMENTS_EXCLUDE:
|
|
66
|
+
const NULLISH_REPLACEMENTS_EXCLUDE: ReadonlyRecord<string, string> = {
|
|
67
67
|
undefined: 'isNotUndefined',
|
|
68
68
|
null: 'isNotNull',
|
|
69
69
|
} as const;
|
|
70
70
|
|
|
71
|
-
const NULLISH_REPLACEMENTS_NARROW:
|
|
71
|
+
const NULLISH_REPLACEMENTS_NARROW: ReadonlyRecord<string, string> = {
|
|
72
72
|
undefined: 'isUndefined',
|
|
73
73
|
null: 'isNull',
|
|
74
74
|
} as const;
|
|
75
75
|
|
|
76
|
-
const GUARD_SPECS: DeepReadonly<
|
|
76
|
+
const GUARD_SPECS: DeepReadonly<ReadonlyRecord<string, GuardSpec>> = {
|
|
77
77
|
isUndefined: { kind: 'narrowTo', atoms: ['undefined'] },
|
|
78
78
|
isNotUndefined: { kind: 'excludeFrom', atoms: ['undefined'] },
|
|
79
79
|
isNull: { kind: 'narrowTo', atoms: ['null'] },
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
type TSESLint,
|
|
5
5
|
type TSESTree,
|
|
6
6
|
} from '@typescript-eslint/utils';
|
|
7
|
+
import { type ReadonlyRecord } from 'ts-type-forge';
|
|
7
8
|
import * as ts from 'typescript';
|
|
8
9
|
import {
|
|
9
10
|
buildImportFixes,
|
|
@@ -110,7 +111,7 @@ const FLIPPED_OPERATORS = {
|
|
|
110
111
|
'<=': '>=',
|
|
111
112
|
'>': '<',
|
|
112
113
|
'>=': '<=',
|
|
113
|
-
} as const satisfies
|
|
114
|
+
} as const satisfies ReadonlyRecord<ComparisonOperator, ComparisonOperator>;
|
|
114
115
|
|
|
115
116
|
/** Classifies `index OP N` (a comparison against an integer literal). */
|
|
116
117
|
const classifyLiteralComparison = (
|
|
@@ -592,10 +593,10 @@ export const preferCanonicalArraySlicing: TSESLint.RuleModule<
|
|
|
592
593
|
|
|
593
594
|
if (
|
|
594
595
|
isArrObject &&
|
|
595
|
-
node.arguments.length === 2 &&
|
|
596
596
|
arrayArg !== undefined &&
|
|
597
|
-
arrayArg.type !== AST_NODE_TYPES.SpreadElement &&
|
|
598
597
|
countArg !== undefined &&
|
|
598
|
+
node.arguments.length === 2 &&
|
|
599
|
+
arrayArg.type !== AST_NODE_TYPES.SpreadElement &&
|
|
599
600
|
asNonNegativeIntegerLiteral(countArg) === 1
|
|
600
601
|
) {
|
|
601
602
|
report(
|
|
@@ -618,12 +619,12 @@ export const preferCanonicalArraySlicing: TSESLint.RuleModule<
|
|
|
618
619
|
const [arg] = node.arguments;
|
|
619
620
|
|
|
620
621
|
if (
|
|
621
|
-
receiver.elements.length === 1 &&
|
|
622
622
|
element !== null &&
|
|
623
623
|
element !== undefined &&
|
|
624
|
+
arg !== undefined &&
|
|
625
|
+
receiver.elements.length === 1 &&
|
|
624
626
|
element.type !== AST_NODE_TYPES.SpreadElement &&
|
|
625
627
|
node.arguments.length === 1 &&
|
|
626
|
-
arg !== undefined &&
|
|
627
628
|
arg.type !== AST_NODE_TYPES.SpreadElement &&
|
|
628
629
|
isArrayOrTupleType(getTypeOf(arg))
|
|
629
630
|
) {
|
|
@@ -691,8 +692,8 @@ export const preferCanonicalArraySlicing: TSESLint.RuleModule<
|
|
|
691
692
|
const [arg] = node.arguments;
|
|
692
693
|
|
|
693
694
|
if (
|
|
694
|
-
node.arguments.length !== 1 ||
|
|
695
695
|
arg === undefined ||
|
|
696
|
+
node.arguments.length !== 1 ||
|
|
696
697
|
arg.type === AST_NODE_TYPES.SpreadElement
|
|
697
698
|
) {
|
|
698
699
|
return;
|
|
@@ -703,9 +704,9 @@ export const preferCanonicalArraySlicing: TSESLint.RuleModule<
|
|
|
703
704
|
const [element] = arg.elements;
|
|
704
705
|
|
|
705
706
|
if (
|
|
706
|
-
arg.elements.length === 1 &&
|
|
707
707
|
element !== null &&
|
|
708
708
|
element !== undefined &&
|
|
709
|
+
arg.elements.length === 1 &&
|
|
709
710
|
element.type !== AST_NODE_TYPES.SpreadElement
|
|
710
711
|
) {
|
|
711
712
|
report(
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
type TSESLint,
|
|
4
4
|
type TSESTree,
|
|
5
5
|
} from '@typescript-eslint/utils';
|
|
6
|
+
import { type MutableRecord, type ReadonlyRecord } from 'ts-type-forge';
|
|
6
7
|
import { getImportedLocalName, getTsDataForgeImport } from './import-utils.mjs';
|
|
7
8
|
import { preferArrIsBoundedLengthArray } from './prefer-arr-is-bounded-length-array.mjs';
|
|
8
9
|
import { preferArrIsFixedLengthArray } from './prefer-arr-is-fixed-length-array.mjs';
|
|
@@ -49,7 +50,7 @@ const MESSAGES = {
|
|
|
49
50
|
preferArrIsBoundedLengthArray.meta.messages.useIsBoundedLengthArray,
|
|
50
51
|
useIsFixedLengthArray:
|
|
51
52
|
preferArrIsFixedLengthArray.meta.messages.useIsFixedLengthArray,
|
|
52
|
-
} as const satisfies
|
|
53
|
+
} as const satisfies ReadonlyRecord<MessageIds, string>;
|
|
53
54
|
|
|
54
55
|
/**
|
|
55
56
|
* Length guards whose bound makes them redundant with a named degenerate
|
|
@@ -180,7 +181,7 @@ const mergeVisitors = (
|
|
|
180
181
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
181
182
|
visitors: readonly TSESLint.RuleListener[],
|
|
182
183
|
): TSESLint.RuleListener => {
|
|
183
|
-
const mut_merged:
|
|
184
|
+
const mut_merged: MutableRecord<string, ((node: never) => void)[]> = {};
|
|
184
185
|
|
|
185
186
|
for (const visitor of visitors) {
|
|
186
187
|
for (const [selector, handler] of Object.entries(visitor)) {
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
type TSESLint,
|
|
4
4
|
type TSESTree,
|
|
5
5
|
} from '@typescript-eslint/utils';
|
|
6
|
-
import { type DeepReadonly } from 'ts-type-forge';
|
|
6
|
+
import { type DeepReadonly, type ReadonlyRecord } from 'ts-type-forge';
|
|
7
7
|
import { buildCalleeResolver, getTsDataForgeImport } from './import-utils.mjs';
|
|
8
8
|
|
|
9
9
|
type Options = readonly [];
|
|
@@ -20,7 +20,7 @@ type ComparisonSpec = Readonly<{
|
|
|
20
20
|
* `xs.filter(isNotUndefined)`). When called with an explicit argument a direct
|
|
21
21
|
* comparison is clearer, so map each to its equivalent operator and literal.
|
|
22
22
|
*/
|
|
23
|
-
const COMPARISON_GUARDS:
|
|
23
|
+
const COMPARISON_GUARDS: ReadonlyRecord<string, ComparisonSpec> = {
|
|
24
24
|
isUndefined: { op: '===', literal: 'undefined' },
|
|
25
25
|
isNotUndefined: { op: '!==', literal: 'undefined' },
|
|
26
26
|
isNull: { op: '===', literal: 'null' },
|