eslint-plugin-ts-type-forge 0.7.3 → 0.8.1

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.
Files changed (49) hide show
  1. package/README.md +31 -8
  2. package/dist/index.mjs +2 -2
  3. package/dist/index.mjs.map +1 -1
  4. package/dist/plugin.d.mts +2 -0
  5. package/dist/plugin.d.mts.map +1 -1
  6. package/dist/plugin.mjs +34 -30
  7. package/dist/plugin.mjs.map +1 -1
  8. package/dist/rule-types.d.mts +15 -0
  9. package/dist/rule-types.d.mts.map +1 -1
  10. package/dist/rule-types.mjs +2 -1
  11. package/dist/rule-types.mjs.map +1 -1
  12. package/dist/rules/ast-utils.mjs +224 -103
  13. package/dist/rules/ast-utils.mjs.map +1 -1
  14. package/dist/rules/constants.mjs +14 -11
  15. package/dist/rules/constants.mjs.map +1 -1
  16. package/dist/rules/import-style.mjs +31 -26
  17. package/dist/rules/import-style.mjs.map +1 -1
  18. package/dist/rules/import-utils.mjs +49 -29
  19. package/dist/rules/import-utils.mjs.map +1 -1
  20. package/dist/rules/index.mjs +2 -2
  21. package/dist/rules/index.mjs.map +1 -1
  22. package/dist/rules/no-side-effect-import.d.mts +19 -0
  23. package/dist/rules/no-side-effect-import.d.mts.map +1 -0
  24. package/dist/rules/no-side-effect-import.mjs +55 -0
  25. package/dist/rules/no-side-effect-import.mjs.map +1 -0
  26. package/dist/rules/prefer-canonical-length-constrained-tuple.mjs +93 -73
  27. package/dist/rules/prefer-canonical-length-constrained-tuple.mjs.map +1 -1
  28. package/dist/rules/prefer-canonical-mutable-record.mjs +88 -75
  29. package/dist/rules/prefer-canonical-mutable-record.mjs.map +1 -1
  30. package/dist/rules/prefer-readonly-or-mutable-record.mjs +41 -45
  31. package/dist/rules/prefer-readonly-or-mutable-record.mjs.map +1 -1
  32. package/dist/rules/prefer-strict-or-relaxed-utility-type.mjs +48 -46
  33. package/dist/rules/prefer-strict-or-relaxed-utility-type.mjs.map +1 -1
  34. package/dist/rules/rewrite-rule-utils.mjs +31 -28
  35. package/dist/rules/rewrite-rule-utils.mjs.map +1 -1
  36. package/dist/rules/rules.d.mts +1 -0
  37. package/dist/rules/rules.d.mts.map +1 -1
  38. package/dist/rules/rules.mjs +8 -9
  39. package/dist/rules/rules.mjs.map +1 -1
  40. package/dist/rules/std-type-rule-utils.mjs +47 -30
  41. package/dist/rules/std-type-rule-utils.mjs.map +1 -1
  42. package/dist/types.mjs +2 -2
  43. package/dist/types.mjs.map +1 -1
  44. package/package.json +6 -7
  45. package/src/plugin.mts +1 -0
  46. package/src/rule-types.mts +16 -0
  47. package/src/rules/no-side-effect-import.mts +73 -0
  48. package/src/rules/no-side-effect-import.test.mts +83 -0
  49. package/src/rules/rules.mts +2 -0
@@ -1,37 +1,54 @@
1
- import { AST_NODE_TYPES } from '@typescript-eslint/utils';
1
+ import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
2
2
  import { Arr } from 'ts-data-forge';
3
3
  import { isLocallyBound } from './ast-utils.mjs';
4
4
  import { resolveTypeName } from './import-style.mjs';
5
5
  import { buildTypeImportFix } from './import-utils.mjs';
6
6
 
7
- const reportStdTypeReference = (context, node, alternatives, messageIds, importStyle) => {
8
- const typeName = node.typeName;
9
- if (typeName.type !== AST_NODE_TYPES.Identifier) return;
10
- const stdName = typeName.name;
11
- const candidates = alternatives.get(stdName);
12
- if (candidates === void 0) return;
13
- if (isLocallyBound(context.sourceCode, node, stdName)) return;
14
- const program = context.sourceCode.ast;
15
- context.report({
16
- node: typeName,
17
- messageId: messageIds.report,
18
- data: { name: stdName, alternatives: formatAlternatives(candidates) },
19
- suggest: candidates.flatMap((candidate) => {
20
- const target = resolveTypeName(program, candidate, importStyle);
21
- return target === void 0 ? [] : [
22
- {
23
- messageId: messageIds.suggestion,
24
- data: { name: stdName, replacement: target.localName },
25
- fix: (fixer) => Arr.toPushed(
26
- target.needsImport ? buildTypeImportFix(fixer, program, candidate) : [],
27
- fixer.replaceText(typeName, target.localName)
28
- )
29
- }
30
- ];
31
- })
32
- });
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+ export const reportStdTypeReference = (context, node, alternatives, messageIds, importStyle) => {
17
+ const typeName = node.typeName;
18
+
19
+ if (typeName.type !== AST_NODE_TYPES.Identifier)
20
+ return;
21
+ const stdName = typeName.name;
22
+ const candidates = alternatives.get(stdName);
23
+ if (candidates === undefined)
24
+ return;
25
+
26
+
27
+ if (isLocallyBound(context.sourceCode, node, stdName))
28
+ return;
29
+ const program = context.sourceCode.ast;
30
+ context.report({
31
+ node: typeName,
32
+ messageId: messageIds.report,
33
+ data: { name: stdName, alternatives: formatAlternatives(candidates) },
34
+ suggest: candidates.flatMap((candidate) => {
35
+ const target = resolveTypeName(program, candidate, importStyle);
36
+
37
+
38
+ return target === undefined
39
+ ? []
40
+ : [
41
+ {
42
+ messageId: messageIds.suggestion,
43
+ data: { name: stdName, replacement: target.localName },
44
+ fix: (fixer) => Arr.toPushed(target.needsImport
45
+ ? buildTypeImportFix(fixer, program, candidate)
46
+ : [], fixer.replaceText(typeName, target.localName)),
47
+ },
48
+ ];
49
+ }),
50
+ });
33
51
  };
34
- const formatAlternatives = (candidates) => candidates.map((candidate) => `\`${candidate}\``).join(" or ");
35
52
 
36
- export { reportStdTypeReference };
37
- //# sourceMappingURL=std-type-rule-utils.mjs.map
53
+ const formatAlternatives = (candidates) => candidates.map((candidate) => `\`${candidate}\``).join(' or ');
54
+ //# sourceMappingURL=std-type-rule-utils.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"std-type-rule-utils.mjs","sources":["../../src/rules/std-type-rule-utils.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { Arr } from 'ts-data-forge';\nimport { isLocallyBound } from './ast-utils.mjs';\nimport { resolveTypeName, type ImportStyle } from './import-style.mjs';\nimport { buildTypeImportFix } from './import-utils.mjs';\n\n/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */\n\n/**\n * The ts-type-forge replacements offered for one standard-library type, in the\n * order they are suggested.\n */\nexport type StdTypeAlternatives = ReadonlyMap<string, readonly string[]>;\n\n/**\n * Reports a reference to a banned standard-library type and offers one\n * suggestion per ts-type-forge alternative.\n *\n * Choosing between the alternatives is a semantic decision — `StrictExclude`\n * additionally checks that the subtrahend is part of the union, `ReadonlyRecord`\n * and `MutableRecord` describe different types — so this is deliberately *not*\n * an autofix. Each suggestion carries its own import fix, which is safe because\n * ESLint applies suggestions one at a time.\n */\nexport const reportStdTypeReference = <MessageIds extends string>(\n context: TSESLint.RuleContext<MessageIds, readonly unknown[]>,\n node: TSESTree.TSTypeReference,\n alternatives: StdTypeAlternatives,\n messageIds: Readonly<{ report: MessageIds; suggestion: MessageIds }>,\n importStyle: ImportStyle,\n): void => {\n const typeName = node.typeName;\n\n // `ns.Pick` / `import('…').Pick` never denote the standard-library type.\n if (typeName.type !== AST_NODE_TYPES.Identifier) return;\n\n const stdName = typeName.name;\n\n const candidates = alternatives.get(stdName);\n\n if (candidates === undefined) return;\n\n // A file that declares — or imports — its own `Pick` does not mean the\n // standard-library one, so there is nothing to steer.\n if (isLocallyBound(context.sourceCode, node, stdName)) return;\n\n const program = context.sourceCode.ast;\n\n context.report({\n node: typeName,\n messageId: messageIds.report,\n data: { name: stdName, alternatives: formatAlternatives(candidates) },\n suggest: candidates.flatMap((candidate) => {\n const target = resolveTypeName(program, candidate, importStyle);\n\n // The replacement name is already bound to something else in this file,\n // so rewriting to it would resolve to the wrong declaration.\n return target === undefined\n ? []\n : [\n {\n messageId: messageIds.suggestion,\n data: { name: stdName, replacement: target.localName },\n fix: (fixer: TSESLint.RuleFixer): readonly TSESLint.RuleFix[] =>\n Arr.toPushed(\n target.needsImport\n ? buildTypeImportFix(fixer, program, candidate)\n : [],\n fixer.replaceText(typeName, target.localName),\n ),\n },\n ];\n }),\n });\n};\n\n/** `['StrictPick', 'RelaxedPick']` → `` '`StrictPick` or `RelaxedPick`' ``. */\nconst formatAlternatives = (candidates: readonly string[]): string =>\n candidates.map((candidate) => `\\`${candidate}\\``).join(' or ');\n"],"names":[],"mappings":";;;;;;AA4BO,MAAM,yBAAyB,CACpC,OAAA,EACA,IAAA,EACA,YAAA,EACA,YACA,WAAA,KACS;AACT,EAAA,MAAM,WAAW,IAAA,CAAK,QAAA;AAGtB,EAAA,IAAI,QAAA,CAAS,IAAA,KAAS,cAAA,CAAe,UAAA,EAAY;AAEjD,EAAA,MAAM,UAAU,QAAA,CAAS,IAAA;AAEzB,EAAA,MAAM,UAAA,GAAa,YAAA,CAAa,GAAA,CAAI,OAAO,CAAA;AAE3C,EAAA,IAAI,eAAe,MAAA,EAAW;AAI9B,EAAA,IAAI,cAAA,CAAe,OAAA,CAAQ,UAAA,EAAY,IAAA,EAAM,OAAO,CAAA,EAAG;AAEvD,EAAA,MAAM,OAAA,GAAU,QAAQ,UAAA,CAAW,GAAA;AAEnC,EAAA,OAAA,CAAQ,MAAA,CAAO;AAAA,IACb,IAAA,EAAM,QAAA;AAAA,IACN,WAAW,UAAA,CAAW,MAAA;AAAA,IACtB,MAAM,EAAE,IAAA,EAAM,SAAS,YAAA,EAAc,kBAAA,CAAmB,UAAU,CAAA,EAAE;AAAA,IACpE,OAAA,EAAS,UAAA,CAAW,OAAA,CAAQ,CAAC,SAAA,KAAc;AACzC,MAAA,MAAM,MAAA,GAAS,eAAA,CAAgB,OAAA,EAAS,SAAA,EAAW,WAAW,CAAA;AAI9D,MAAA,OAAO,MAAA,KAAW,MAAA,GACd,EAAC,GACD;AAAA,QACE;AAAA,UACE,WAAW,UAAA,CAAW,UAAA;AAAA,UACtB,MAAM,EAAE,IAAA,EAAM,OAAA,EAAS,WAAA,EAAa,OAAO,SAAA,EAAU;AAAA,UACrD,GAAA,EAAK,CAAC,KAAA,KACJ,GAAA,CAAI,QAAA;AAAA,YACF,OAAO,WAAA,GACH,kBAAA,CAAmB,OAAO,OAAA,EAAS,SAAS,IAC5C,EAAC;AAAA,YACL,KAAA,CAAM,WAAA,CAAY,QAAA,EAAU,MAAA,CAAO,SAAS;AAAA;AAC9C;AACJ,OACF;AAAA,IACN,CAAC;AAAA,GACF,CAAA;AACH;AAGA,MAAM,kBAAA,GAAqB,CAAC,UAAA,KAC1B,UAAA,CAAW,GAAA,CAAI,CAAC,SAAA,KAAc,CAAA,EAAA,EAAK,SAAS,CAAA,EAAA,CAAI,CAAA,CAAE,IAAA,CAAK,MAAM,CAAA;;;;"}
1
+ {"version":3,"file":"std-type-rule-utils.mjs","sourceRoot":"","sources":["../../src/rules/std-type-rule-utils.mts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,GAGf,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAoB,MAAM,oBAAoB,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAUxD;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CACpC,OAA6D,EAC7D,IAA8B,EAC9B,YAAiC,EACjC,UAAoE,EACpE,WAAwB,EAClB,EAAE;IACR,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;IAE/B,yEAAyE;IACzE,IAAI,QAAQ,CAAC,IAAI,KAAK,cAAc,CAAC,UAAU;QAAE,OAAO;IAExD,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC;IAE9B,MAAM,UAAU,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAE7C,IAAI,UAAU,KAAK,SAAS;QAAE,OAAO;IAErC,uEAAuE;IACvE,sDAAsD;IACtD,IAAI,cAAc,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,EAAE,OAAO,CAAC;QAAE,OAAO;IAE9D,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;IAEvC,OAAO,CAAC,MAAM,CAAC;QACb,IAAI,EAAE,QAAQ;QACd,SAAS,EAAE,UAAU,CAAC,MAAM;QAC5B,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,kBAAkB,CAAC,UAAU,CAAC,EAAE;QACrE,OAAO,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,SAAS,EAAE,EAAE;YACxC,MAAM,MAAM,GAAG,eAAe,CAAC,OAAO,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;YAEhE,wEAAwE;YACxE,6DAA6D;YAC7D,OAAO,MAAM,KAAK,SAAS;gBACzB,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC;oBACE;wBACE,SAAS,EAAE,UAAU,CAAC,UAAU;wBAChC,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,CAAC,SAAS,EAAE;wBACtD,GAAG,EAAE,CAAC,KAAyB,EAA+B,EAAE,CAC9D,GAAG,CAAC,QAAQ,CACV,MAAM,CAAC,WAAW;4BAChB,CAAC,CAAC,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,CAAC;4BAC/C,CAAC,CAAC,EAAE,EACN,KAAK,CAAC,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC,SAAS,CAAC,CAC9C;qBACJ;iBACF,CAAC;QACR,CAAC,CAAC;KACH,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,+EAA+E;AAC/E,MAAM,kBAAkB,GAAG,CAAC,UAA6B,EAAU,EAAE,CACnE,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,KAAK,SAAS,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC"}
package/dist/types.mjs CHANGED
@@ -1,2 +1,2 @@
1
-
2
- //# sourceMappingURL=types.mjs.map
1
+ export {};
2
+ //# sourceMappingURL=types.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
1
+ {"version":3,"file":"types.mjs","sourceRoot":"","sources":["../src/types.mts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-ts-type-forge",
3
- "version": "0.7.3",
3
+ "version": "0.8.1",
4
4
  "private": false,
5
5
  "description": "ESLint rules that steer type declarations toward ts-type-forge idioms.",
6
6
  "keywords": [
@@ -36,21 +36,20 @@
36
36
  ],
37
37
  "dependencies": {
38
38
  "@typescript-eslint/utils": "~8.68.0",
39
- "ts-data-forge": "~14.6.1",
39
+ "ts-data-forge": "~14.6.3",
40
40
  "ts-type-forge": "9.2.2"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@typescript-eslint/parser": "8.68.0",
44
44
  "@typescript-eslint/rule-tester": "8.68.0",
45
45
  "dedent": "1.7.2",
46
- "eslint-config-typed": "5.10.2",
47
- "eslint-plugin-ts-data-forge": "0.5.4",
48
- "eslint-plugin-ts-fortress": "1.0.7",
46
+ "eslint-config-typed": "5.11.2",
47
+ "eslint-plugin-ts-data-forge": "0.7.0",
48
+ "eslint-plugin-ts-fortress": "1.2.0",
49
49
  "prettier": "3.9.6",
50
50
  "prettier-plugin-organize-imports": "4.3.0",
51
51
  "prettier-plugin-packagejson": "3.0.2",
52
- "rollup": "4.62.5",
53
- "ts-repo-utils": "10.4.0"
52
+ "ts-repo-utils": "10.6.0"
54
53
  },
55
54
  "peerDependencies": {
56
55
  "eslint": ">=9.0.0",
package/src/plugin.mts CHANGED
@@ -8,6 +8,7 @@ import { type ESLintFlatConfig, type ESLintPlugin } from './types.mjs';
8
8
  * a rule without listing it here fails to type-check.
9
9
  */
10
10
  const recommendedRules = {
11
+ 'ts-type-forge/no-side-effect-import': 'error',
11
12
  'ts-type-forge/prefer-canonical-length-constrained-tuple': 'error',
12
13
  'ts-type-forge/prefer-canonical-mutable-record': 'error',
13
14
  'ts-type-forge/prefer-readonly-or-mutable-record': 'error',
@@ -27,6 +27,21 @@ type RuleOptionsOf<Name extends keyof typeof tsTypeForgeRules> =
27
27
  ? Options
28
28
  : never;
29
29
 
30
+ /**
31
+ * @description Remove a side-effect-only `import 'ts-type-forge';`, which binds no name and has no runtime entry point to resolve to. `ts-type-forge/global` is left alone.
32
+ *
33
+ * ```md
34
+ * | key | value |
35
+ * | :--------- | :------ |
36
+ * | type | problem |
37
+ * | deprecated | false |
38
+ * | fixable | code |
39
+ * ```
40
+ */
41
+ namespace NoSideEffectImport {
42
+ export type RuleEntry = Linter.StringSeverity;
43
+ }
44
+
30
45
  /**
31
46
  * @description Replace uniform tuple spellings such as `[V, ...V[]]` / `[V, V, ...V[]]` / `[V, V, V]` with the canonical ts-type-forge tuple type.
32
47
  *
@@ -209,6 +224,7 @@ namespace PreferStrictOrRelaxedUtilityType {
209
224
  }
210
225
 
211
226
  export type EslintTsTypeForgeRules = Readonly<{
227
+ 'ts-type-forge/no-side-effect-import': NoSideEffectImport.RuleEntry;
212
228
  'ts-type-forge/prefer-canonical-length-constrained-tuple': PreferCanonicalLengthConstrainedTuple.RuleEntry;
213
229
  'ts-type-forge/prefer-canonical-mutable-record': PreferCanonicalMutableRecord.RuleEntry;
214
230
  'ts-type-forge/prefer-readonly-or-mutable-record': PreferReadonlyOrMutableRecord.RuleEntry;
@@ -0,0 +1,73 @@
1
+ import { type TSESLint, type TSESTree } from '@typescript-eslint/utils';
2
+ import { Arr } from 'ts-data-forge';
3
+ import { TS_TYPE_FORGE_MODULE } from './constants.mjs';
4
+
5
+ type Options = readonly [];
6
+
7
+ type MessageIds = 'removeSideEffectImport';
8
+
9
+ /**
10
+ * `import 'ts-type-forge';` binds nothing, and there is nothing for it to run:
11
+ * the package ships declarations only, and its `exports` map offers no runtime
12
+ * condition at all, so the import fails to resolve the moment the module graph
13
+ * is loaded. TypeScript keeps a side-effect import in the emitted JavaScript —
14
+ * that is the one kind it never elides — so this is a runtime error waiting in
15
+ * a file that type-checks.
16
+ *
17
+ * The ambient globals are a separate entry point, `ts-type-forge/global`,
18
+ * usually brought in through `compilerOptions.types` or a triple-slash
19
+ * reference. This rule matches the bare specifier exactly, so that subpath is
20
+ * left alone.
21
+ */
22
+ export const noSideEffectImport: TSESLint.RuleModule<MessageIds, Options> = {
23
+ meta: {
24
+ type: 'problem',
25
+ docs: {
26
+ description: `Remove a side-effect-only \`import '${TS_TYPE_FORGE_MODULE}';\`, which binds no name and has no runtime entry point to resolve to. \`${TS_TYPE_FORGE_MODULE}/global\` is left alone.`,
27
+ },
28
+ fixable: 'code',
29
+ schema: [],
30
+ messages: {
31
+ removeSideEffectImport: `\`import '${TS_TYPE_FORGE_MODULE}';\` binds no name, and \`${TS_TYPE_FORGE_MODULE}\` ships types only — it has no runtime entry point, so the import throws when the module is loaded. Remove it; the ambient globals come from \`${TS_TYPE_FORGE_MODULE}/global\`, not from this.`,
32
+ },
33
+ },
34
+
35
+ create: (context) => ({
36
+ ImportDeclaration: (node) => {
37
+ if (
38
+ node.source.value !== TS_TYPE_FORGE_MODULE ||
39
+ !Arr.isEmpty(node.specifiers)
40
+ ) {
41
+ return;
42
+ }
43
+
44
+ context.report({
45
+ node,
46
+ messageId: 'removeSideEffectImport',
47
+ fix: (fixer) => removeStatement(fixer, context.sourceCode, node),
48
+ });
49
+ },
50
+ }),
51
+ defaultOptions: [],
52
+ } as const;
53
+
54
+ /* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */
55
+
56
+ /**
57
+ * Removes a statement along with the rest of its line, so that deleting it does
58
+ * not leave a blank line where it was.
59
+ */
60
+ const removeStatement = (
61
+ fixer: TSESLint.RuleFixer,
62
+ sourceCode: TSESLint.SourceCode,
63
+ node: TSESTree.Node,
64
+ ): TSESLint.RuleFix => {
65
+ const restOfLine = /^[^\S\n]*\n/u.exec(
66
+ sourceCode.getText().slice(node.range[1]),
67
+ );
68
+
69
+ return fixer.removeRange([
70
+ node.range[0],
71
+ node.range[1] + (restOfLine?.[0].length ?? 0),
72
+ ]);
73
+ };
@@ -0,0 +1,83 @@
1
+ import parser from '@typescript-eslint/parser';
2
+ import { RuleTester } from '@typescript-eslint/rule-tester';
3
+ import dedent from 'dedent';
4
+ import { noSideEffectImport } from './no-side-effect-import.mjs';
5
+
6
+ const tester = new RuleTester({
7
+ languageOptions: {
8
+ parser,
9
+ parserOptions: { ecmaVersion: 2020, sourceType: 'module' },
10
+ },
11
+ });
12
+
13
+ describe('no-side-effect-import', () => {
14
+ tester.run('no-side-effect-import', noSideEffectImport, {
15
+ valid: [
16
+ {
17
+ name: 'accepts a named import',
18
+ code: dedent`
19
+ import { type NonEmptyArray } from 'ts-type-forge';
20
+
21
+ export type Names = NonEmptyArray<string>;
22
+ `,
23
+ },
24
+ {
25
+ name: 'leaves the ambient globals entry point alone',
26
+ code: dedent`
27
+ import 'ts-type-forge/global';
28
+ `,
29
+ },
30
+ {
31
+ name: 'leaves other side-effect imports alone',
32
+ code: dedent`
33
+ import './polyfills.mjs';
34
+ import 'ts-type-forge-extras';
35
+ `,
36
+ },
37
+ ],
38
+ invalid: [
39
+ {
40
+ name: 'removes a side-effect-only import',
41
+ code: dedent`
42
+ import 'ts-type-forge';
43
+ `,
44
+ output: '',
45
+ errors: [{ messageId: 'removeSideEffectImport' }],
46
+ },
47
+ {
48
+ name: 'removes it along with its line',
49
+ code: dedent`
50
+ import 'ts-type-forge';
51
+ import { type Uint32 } from 'ts-type-forge';
52
+
53
+ export type Size = Uint32;
54
+ `,
55
+ output: dedent`
56
+ import { type Uint32 } from 'ts-type-forge';
57
+
58
+ export type Size = Uint32;
59
+ `,
60
+ errors: [{ messageId: 'removeSideEffectImport' }],
61
+ },
62
+ {
63
+ name: 'removes every side-effect-only import',
64
+ code: dedent`
65
+ import 'ts-type-forge';
66
+ import 'ts-type-forge/global';
67
+ import 'ts-type-forge';
68
+
69
+ export type Size = number;
70
+ `,
71
+ output: dedent`
72
+ import 'ts-type-forge/global';
73
+
74
+ export type Size = number;
75
+ `,
76
+ errors: [
77
+ { messageId: 'removeSideEffectImport' },
78
+ { messageId: 'removeSideEffectImport' },
79
+ ],
80
+ },
81
+ ],
82
+ });
83
+ });
@@ -1,10 +1,12 @@
1
1
  import { type ESLintPlugin } from '../types.mjs';
2
+ import { noSideEffectImport } from './no-side-effect-import.mjs';
2
3
  import { preferCanonicalLengthConstrainedTuple } from './prefer-canonical-length-constrained-tuple.mjs';
3
4
  import { preferCanonicalMutableRecord } from './prefer-canonical-mutable-record.mjs';
4
5
  import { preferReadonlyOrMutableRecord } from './prefer-readonly-or-mutable-record.mjs';
5
6
  import { preferStrictOrRelaxedUtilityType } from './prefer-strict-or-relaxed-utility-type.mjs';
6
7
 
7
8
  export const tsTypeForgeRules = {
9
+ 'no-side-effect-import': noSideEffectImport,
8
10
  'prefer-canonical-length-constrained-tuple':
9
11
  preferCanonicalLengthConstrainedTuple,
10
12
  'prefer-canonical-mutable-record': preferCanonicalMutableRecord,