eslint-plugin-ts-std-forge 0.1.14 → 0.1.16

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.
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-safe-array-length-guard.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-safe-array-length-guard.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;AAYlC,KAAK,OAAO,GAAG,SAAS,EAAE,CAAC;AAE3B,KAAK,UAAU,GAAG,YAAY,GAAG,eAAe,CAAC;AAwCjD,eAAO,MAAM,0BAA0B,EAAE,QAAQ,CAAC,UAAU,CAC1D,UAAU,EACV,OAAO,CA2GC,CAAC"}
1
+ {"version":3,"file":"prefer-safe-array-length-guard.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-safe-array-length-guard.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;AAWlC,KAAK,OAAO,GAAG,SAAS,EAAE,CAAC;AAE3B,KAAK,UAAU,GAAG,YAAY,GAAG,eAAe,CAAC;AAwCjD,eAAO,MAAM,0BAA0B,EAAE,QAAQ,CAAC,UAAU,CAC1D,UAAU,EACV,OAAO,CA8GC,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
2
- import { buildNamedImportFix, getArgumentText, getImportDeclarations, getNamedImports, skipTypeWrappers, typeHoldsAtEveryLayer, } from 'eslint-rule-forge';
2
+ import { bindValueImports, getArgumentText, hasForeignBinding, skipTypeWrappers, typeHoldsAtEveryLayer, } from 'eslint-rule-forge';
3
3
  import { TS_STD_FORGE_MODULE } from './constants.mjs';
4
4
 
5
5
 
@@ -39,15 +39,13 @@ export const preferSafeArrayLengthGuard = {
39
39
  fixable: 'code',
40
40
  schema: [],
41
41
  messages: {
42
- useIsEmpty: 'Replace `{{original}}` with `SafeArray.isEmpty({{arrayName}})` from ts-std-forge: the comparison narrows nothing, the guard narrows to `readonly []`.',
43
- useIsNonEmpty: 'Replace `{{original}}` with `SafeArray.isNonEmpty({{arrayName}})` from ts-std-forge: the comparison narrows nothing, so `{{arrayName}}[0]` stays `| undefined`.',
42
+ useIsEmpty: 'Replace `{{original}}` with `{{safeArray}}.isEmpty({{arrayName}})` from ts-std-forge: the comparison narrows nothing, the guard narrows to `readonly []`.',
43
+ useIsNonEmpty: 'Replace `{{original}}` with `{{safeArray}}.isNonEmpty({{arrayName}})` from ts-std-forge: the comparison narrows nothing, so `{{arrayName}}[0]` stays `| undefined`.',
44
44
  },
45
45
  defaultOptions: [],
46
46
  },
47
47
  create: (context) => {
48
48
  const sourceCode = context.sourceCode;
49
- const program = sourceCode.ast;
50
- const tsStdForgeImports = getImportDeclarations(program, TS_STD_FORGE_MODULE);
51
49
 
52
50
 
53
51
 
@@ -72,36 +70,47 @@ export const preferSafeArrayLengthGuard = {
72
70
  mut_nodesToFix.push({ node, ...matched });
73
71
  },
74
72
  'Program:exit': () => {
75
- const hasSafeArrayImport = getNamedImports(tsStdForgeImports).includes('SafeArray');
76
-
77
-
78
-
73
+ if (mut_nodesToFix.length === 0) {
74
+ return;
75
+ }
76
+ const binding = bindValueImports(sourceCode, TS_STD_FORGE_MODULE, [
77
+ 'SafeArray',
78
+ ]);
79
+ const localName = binding?.localNames[0];
79
80
 
80
81
 
81
- for (const [index, { node, arrayExpression, guard },] of mut_nodesToFix.entries()) {
82
+ for (const { node, arrayExpression, guard } of mut_nodesToFix) {
82
83
  const arrayText = getArgumentText(sourceCode, arrayExpression);
84
+ const writable = binding !== undefined &&
85
+ localName !== undefined &&
86
+ !hasForeignBinding(sourceCode, node, localName, TS_STD_FORGE_MODULE);
83
87
  context.report({
84
88
  node,
85
89
  messageId: MESSAGE_IDS[guard],
86
90
  data: {
87
91
  original: sourceCode.getText(node),
88
92
  arrayName: arrayText,
93
+
94
+ safeArray: writable ? localName : 'SafeArray',
89
95
  },
90
- fix: (fixer) => {
91
- const replacement = `SafeArray.${guard}(${arrayText})`;
92
- const importFixes = index === 0 && !hasSafeArrayImport
93
- ? buildNamedImportFix(fixer, program, TS_STD_FORGE_MODULE, [
94
- 'SafeArray',
95
- ])
96
- : [];
97
- return [...importFixes, fixer.replaceText(node, replacement)];
98
- },
96
+ fix: writable
97
+ ? (fixer) => [
98
+ ...binding.edits(fixer),
99
+ fixer.replaceText(node, `${localName}.${guard}(${arrayText})`),
100
+ ]
101
+ : null,
99
102
  });
100
103
  }
101
104
  },
102
105
  };
103
106
  },
104
107
  };
108
+ const isLengthAccess = (
109
+
110
+ node) => node.type === AST_NODE_TYPES.MemberExpression &&
111
+ !node.computed &&
112
+ node.property.type === AST_NODE_TYPES.Identifier &&
113
+ node.property.name === 'length';
105
114
 
106
115
 
107
116
 
@@ -134,10 +143,4 @@ node) => {
134
143
  ? undefined
135
144
  : { arrayExpression: lengthSide.object, guard: matched.guard };
136
145
  };
137
- const isLengthAccess = (
138
-
139
- node) => node.type === AST_NODE_TYPES.MemberExpression &&
140
- !node.computed &&
141
- node.property.type === AST_NODE_TYPES.Identifier &&
142
- node.property.name === 'length';
143
146
  //# sourceMappingURL=prefer-safe-array-length-guard.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-safe-array-length-guard.mjs","sourceRoot":"","sources":["../../src/rules/prefer-safe-array-length-guard.mts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,GAGf,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,qBAAqB,EACrB,eAAe,EACf,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAQtD;;;;;;;;;GASG;AACH,MAAM,WAAW,GAAG;IAClB,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE;IAC/C,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE;IAClD,EAAE,QAAQ,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE;IAChD,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE;CAK/C,CAAC;AAEL,wEAAwE;AACxE,MAAM,OAAO,GAAmC;IAC9C,KAAK,EAAE,KAAK;IACZ,KAAK,EAAE,KAAK;IACZ,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;CACF,CAAC;AAEX,MAAM,WAAW,GAAsC;IACrD,OAAO,EAAE,YAAY;IACrB,UAAU,EAAE,eAAe;CACnB,CAAC;AAEX,MAAM,CAAC,MAAM,0BAA0B,GAGnC;IACF,IAAI,EAAE;QACJ,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE;YACJ,WAAW,EACT,+LAA+L;SAClM;QACD,OAAO,EAAE,MAAM;QACf,MAAM,EAAE,EAAE;QACV,QAAQ,EAAE;YACR,UAAU,EACR,uJAAuJ;YACzJ,aAAa,EACX,iKAAiK;SACpK;QACD,cAAc,EAAE,EAAE;KACnB;IAED,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE;QAClB,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QAEtC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC;QAE/B,MAAM,iBAAiB,GAAG,qBAAqB,CAC7C,OAAO,EACP,mBAAmB,CACpB,CAAC;QAEF;;;;;;;WAOG;QACH,MAAM,WAAW,GAAG;QAClB,8EAA8E;QAC9E,UAA+B,EACtB,EAAE,CACX,qBAAqB,CACnB,UAAU,EACV,UAAU,EACV,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CAChB,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CACzD,CAAC;QAEJ,MAAM,cAAc,GAId,EAAE,CAAC;QAET,OAAO;YACL,gBAAgB,EAAE,CAAC,IAAI,EAAE,EAAE;gBACzB,MAAM,OAAO,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;gBAE5C,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;oBAC1B,OAAO;gBACT,CAAC;gBAED,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC;oBAC1C,OAAO;gBACT,CAAC;gBAED,cAAc,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;YAC5C,CAAC;YACD,cAAc,EAAE,GAAG,EAAE;gBACnB,MAAM,kBAAkB,GACtB,eAAe,CAAC,iBAAiB,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;gBAE3D,iDAAiD;gBACjD,6DAA6D;gBAC7D,sEAAsE;gBACtE,qEAAqE;gBACrE,WAAW;gBACX,KAAK,MAAM,CACT,KAAK,EACL,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,EACjC,IAAI,cAAc,CAAC,OAAO,EAAE,EAAE,CAAC;oBAC9B,MAAM,SAAS,GAAG,eAAe,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;oBAE/D,OAAO,CAAC,MAAM,CAAC;wBACb,IAAI;wBACJ,SAAS,EAAE,WAAW,CAAC,KAAK,CAAC;wBAC7B,IAAI,EAAE;4BACJ,QAAQ,EAAE,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC;4BAClC,SAAS,EAAE,SAAS;yBACrB;wBACD,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE;4BACb,MAAM,WAAW,GAAG,aAAa,KAAK,IAAI,SAAS,GAAG,CAAC;4BAEvD,MAAM,WAAW,GACf,KAAK,KAAK,CAAC,IAAI,CAAC,kBAAkB;gCAChC,CAAC,CAAC,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE;oCACvD,WAAW;iCACZ,CAAC;gCACJ,CAAC,CAAC,EAAE,CAAC;4BAET,OAAO,CAAC,GAAG,WAAW,EAAE,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC;wBAChE,CAAC;qBACF,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACO,CAAC;AAEX;;;;GAIG;AACH,MAAM,qBAAqB,GAAG;AAC5B,6EAA6E;AAC7E,kEAAkE;AAClE,8EAA8E;AAC9E,IAA+B,EAGnB,EAAE;IACd,2EAA2E;IAC3E,oEAAoE;IACpE,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEzC,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAE3C,MAAM,YAAY,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IAE1C,MAAM,UAAU,GAAG,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;IAE/C,MAAM,SAAS,GAAG,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IAE9C,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,EAAE,CAAC;QAChC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,uEAAuE;IACvE,8CAA8C;IAC9C,IACE,SAAS,CAAC,IAAI,KAAK,cAAc,CAAC,OAAO;QACzC,OAAO,SAAS,CAAC,KAAK,KAAK,QAAQ,EACnC,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,QAAQ,GAAG,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAEvE,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAC9B,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,KAAK,QAAQ,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,KAAK,CAC1E,CAAC;IAEF,OAAO,OAAO,KAAK,SAAS;QAC1B,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,EAAE,eAAe,EAAE,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;AACnE,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG;AACrB,8EAA8E;AAC9E,IAAmB,EACgB,EAAE,CACrC,IAAI,CAAC,IAAI,KAAK,cAAc,CAAC,gBAAgB;IAC7C,CAAC,IAAI,CAAC,QAAQ;IACd,IAAI,CAAC,QAAQ,CAAC,IAAI,KAAK,cAAc,CAAC,UAAU;IAChD,IAAI,CAAC,QAAQ,CAAC,IAAI,KAAK,QAAQ,CAAC"}
1
+ {"version":3,"file":"prefer-safe-array-length-guard.mjs","sourceRoot":"","sources":["../../src/rules/prefer-safe-array-length-guard.mts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,GAGf,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAQtD;;;;;;;;;GASG;AACH,MAAM,WAAW,GAAG;IAClB,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE;IAC/C,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE;IAClD,EAAE,QAAQ,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE;IAChD,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE;CAK/C,CAAC;AAEL,wEAAwE;AACxE,MAAM,OAAO,GAAmC;IAC9C,KAAK,EAAE,KAAK;IACZ,KAAK,EAAE,KAAK;IACZ,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;CACF,CAAC;AAEX,MAAM,WAAW,GAAsC;IACrD,OAAO,EAAE,YAAY;IACrB,UAAU,EAAE,eAAe;CACnB,CAAC;AAEX,MAAM,CAAC,MAAM,0BAA0B,GAGnC;IACF,IAAI,EAAE;QACJ,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE;YACJ,WAAW,EACT,+LAA+L;SAClM;QACD,OAAO,EAAE,MAAM;QACf,MAAM,EAAE,EAAE;QACV,QAAQ,EAAE;YACR,UAAU,EACR,2JAA2J;YAC7J,aAAa,EACX,qKAAqK;SACxK;QACD,cAAc,EAAE,EAAE;KACnB;IAED,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE;QAClB,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QAEtC;;;;;;;WAOG;QACH,MAAM,WAAW,GAAG;QAClB,8EAA8E;QAC9E,UAA+B,EACtB,EAAE,CACX,qBAAqB,CACnB,UAAU,EACV,UAAU,EACV,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CAChB,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CACzD,CAAC;QAEJ,MAAM,cAAc,GAId,EAAE,CAAC;QAET,OAAO;YACL,gBAAgB,EAAE,CAAC,IAAI,EAAE,EAAE;gBACzB,MAAM,OAAO,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;gBAE5C,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;oBAC1B,OAAO;gBACT,CAAC;gBAED,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC;oBAC1C,OAAO;gBACT,CAAC;gBAED,cAAc,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;YAC5C,CAAC;YACD,cAAc,EAAE,GAAG,EAAE;gBACnB,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAChC,OAAO;gBACT,CAAC;gBAED,MAAM,OAAO,GAAG,gBAAgB,CAAC,UAAU,EAAE,mBAAmB,EAAE;oBAChE,WAAW;iBACZ,CAAC,CAAC;gBAEH,MAAM,SAAS,GAAG,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;gBAEzC,sEAAsE;gBACtE,6CAA6C;gBAC7C,KAAK,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,IAAI,cAAc,EAAE,CAAC;oBAC9D,MAAM,SAAS,GAAG,eAAe,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;oBAE/D,MAAM,QAAQ,GACZ,OAAO,KAAK,SAAS;wBACrB,SAAS,KAAK,SAAS;wBACvB,CAAC,iBAAiB,CAChB,UAAU,EACV,IAAI,EACJ,SAAS,EACT,mBAAmB,CACpB,CAAC;oBAEJ,OAAO,CAAC,MAAM,CAAC;wBACb,IAAI;wBACJ,SAAS,EAAE,WAAW,CAAC,KAAK,CAAC;wBAC7B,IAAI,EAAE;4BACJ,QAAQ,EAAE,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC;4BAClC,SAAS,EAAE,SAAS;4BACpB,0DAA0D;4BAC1D,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW;yBAC9C;wBACD,GAAG,EAAE,QAAQ;4BACX,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;gCACT,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC;gCACvB,KAAK,CAAC,WAAW,CACf,IAAI,EACJ,GAAG,SAAS,IAAI,KAAK,IAAI,SAAS,GAAG,CACtC;6BACF;4BACH,CAAC,CAAC,IAAI;qBACT,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACO,CAAC;AAEX,MAAM,cAAc,GAAG;AACrB,8EAA8E;AAC9E,IAAmB,EACgB,EAAE,CACrC,IAAI,CAAC,IAAI,KAAK,cAAc,CAAC,gBAAgB;IAC7C,CAAC,IAAI,CAAC,QAAQ;IACd,IAAI,CAAC,QAAQ,CAAC,IAAI,KAAK,cAAc,CAAC,UAAU;IAChD,IAAI,CAAC,QAAQ,CAAC,IAAI,KAAK,QAAQ,CAAC;AAElC;;;;GAIG;AACH,MAAM,qBAAqB,GAAG;AAC5B,6EAA6E;AAC7E,kEAAkE;AAClE,8EAA8E;AAC9E,IAA+B,EAGnB,EAAE;IACd,2EAA2E;IAC3E,oEAAoE;IACpE,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEzC,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAE3C,MAAM,YAAY,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IAE1C,MAAM,UAAU,GAAG,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;IAE/C,MAAM,SAAS,GAAG,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IAE9C,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,EAAE,CAAC;QAChC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,uEAAuE;IACvE,8CAA8C;IAC9C,IACE,SAAS,CAAC,IAAI,KAAK,cAAc,CAAC,OAAO;QACzC,OAAO,SAAS,CAAC,KAAK,KAAK,QAAQ,EACnC,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,QAAQ,GAAG,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAEvE,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAC9B,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,KAAK,QAAQ,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,KAAK,CAC1E,CAAC;IAEF,OAAO,OAAO,KAAK,SAAS;QAC1B,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,EAAE,eAAe,EAAE,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;AACnE,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-ts-std-forge",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "private": false,
5
5
  "description": "ESLint rules that steer code toward ts-std-forge idioms.",
6
6
  "keywords": [
@@ -35,7 +35,7 @@
35
35
  ],
36
36
  "dependencies": {
37
37
  "@typescript-eslint/utils": "^8.72.0",
38
- "eslint-rule-forge": "^0.3.2",
38
+ "eslint-rule-forge": "^0.4.0",
39
39
  "ts-std-forge": "0.5.2",
40
40
  "ts-type-forge": "~9.2.2"
41
41
  },
@@ -43,9 +43,9 @@
43
43
  "@typescript-eslint/parser": "8.72.0",
44
44
  "@typescript-eslint/rule-tester": "8.72.0",
45
45
  "dedent": "1.7.2",
46
- "eslint-config-typed": "5.20.1",
47
- "eslint-plugin-ts-fortress": "1.3.9",
48
- "eslint-plugin-ts-type-forge": "0.8.14",
46
+ "eslint-config-typed": "7.2.0",
47
+ "eslint-plugin-ts-fortress": "1.3.11",
48
+ "eslint-plugin-ts-type-forge": "0.8.15",
49
49
  "prettier": "3.9.10",
50
50
  "prettier-plugin-organize-imports": "4.3.0",
51
51
  "prettier-plugin-packagejson": "3.0.2",
@@ -180,14 +180,24 @@ describe('prefer-is-non-null-object in other forms', () => {
180
180
  const y = a && typeof u === 'object' && u !== null;
181
181
  const z = a && u !== null && (typeof u === 'object') && b;
182
182
  `,
183
- output: dedent`
184
- import { isNonNullObject } from 'ts-std-forge';
185
- declare const a: boolean;
186
- declare const b: boolean;
187
- const x = isNonNullObject(u) && 'k' in u;
188
- const y = a && isNonNullObject(u);
189
- const z = a && isNonNullObject(u) && b;
190
- `,
183
+ output: [
184
+ dedent`
185
+ import { isNonNullObject } from 'ts-std-forge';
186
+ declare const a: boolean;
187
+ declare const b: boolean;
188
+ const x = isNonNullObject(u) && 'k' in u;
189
+ const y = a && typeof u === 'object' && u !== null;
190
+ const z = a && u !== null && (typeof u === 'object') && b;
191
+ `,
192
+ dedent`
193
+ import { isNonNullObject } from 'ts-std-forge';
194
+ declare const a: boolean;
195
+ declare const b: boolean;
196
+ const x = isNonNullObject(u) && 'k' in u;
197
+ const y = a && isNonNullObject(u);
198
+ const z = a && isNonNullObject(u) && b;
199
+ `,
200
+ ],
191
201
  errors: [
192
202
  { messageId: 'useIsNonNullObject' },
193
203
  { messageId: 'useIsNonNullObject' },
@@ -200,11 +210,18 @@ describe('prefer-is-non-null-object in other forms', () => {
200
210
  declare const v: unknown;
201
211
  const ok = typeof u === 'object' && u !== null && v !== null && typeof v === 'object';
202
212
  `,
203
- output: dedent`
204
- import { isNonNullObject } from 'ts-std-forge';
205
- declare const v: unknown;
206
- const ok = isNonNullObject(u) && isNonNullObject(v);
207
- `,
213
+ output: [
214
+ dedent`
215
+ import { isNonNullObject } from 'ts-std-forge';
216
+ declare const v: unknown;
217
+ const ok = isNonNullObject(u) && v !== null && typeof v === 'object';
218
+ `,
219
+ dedent`
220
+ import { isNonNullObject } from 'ts-std-forge';
221
+ declare const v: unknown;
222
+ const ok = isNonNullObject(u) && isNonNullObject(v);
223
+ `,
224
+ ],
208
225
  errors: [
209
226
  { messageId: 'useIsNonNullObject' },
210
227
  { messageId: 'useIsNonNullObject' },
@@ -85,13 +85,22 @@ describe('prefer-is-record-and-has-key', () => {
85
85
  const a = Object.hasOwn(rec, 'a');
86
86
  const b = Object.hasOwn(unk, 'b');
87
87
  `,
88
- output: dedent`
89
- import { isRecord, hasKey } from 'ts-std-forge';
90
- declare const rec: Record<string, unknown>;
91
- declare const unk: unknown;
92
- const a = hasKey(rec, 'a');
93
- const b = (isRecord(unk) && hasKey(unk, 'b'));
94
- `,
88
+ output: [
89
+ dedent`
90
+ import { hasKey } from 'ts-std-forge';
91
+ declare const rec: Record<string, unknown>;
92
+ declare const unk: unknown;
93
+ const a = hasKey(rec, 'a');
94
+ const b = Object.hasOwn(unk, 'b');
95
+ `,
96
+ dedent`
97
+ import { hasKey, isRecord } from 'ts-std-forge';
98
+ declare const rec: Record<string, unknown>;
99
+ declare const unk: unknown;
100
+ const a = hasKey(rec, 'a');
101
+ const b = (isRecord(unk) && hasKey(unk, 'b'));
102
+ `,
103
+ ],
95
104
  errors: [
96
105
  { messageId: 'useHasKey' },
97
106
  { messageId: 'useIsRecordAndHasKey' },
@@ -67,16 +67,23 @@ ruleTester.run('prefer-safe-array-is-array', preferSafeArrayIsArray, {
67
67
  errors: [{ messageId: 'useIsArray' }],
68
68
  },
69
69
  {
70
- name: 'Replace multiple Array.isArray calls, importing once',
70
+ name: 'Replace multiple Array.isArray calls, each fix importing',
71
71
  code: dedent`
72
72
  const result1 = Array.isArray(value1);
73
73
  const result2 = Array.isArray(value2);
74
74
  `,
75
- output: dedent`
76
- import { SafeArray } from 'ts-std-forge';
77
- const result1 = SafeArray.isArray(value1);
78
- const result2 = SafeArray.isArray(value2);
79
- `,
75
+ output: [
76
+ dedent`
77
+ import { SafeArray } from 'ts-std-forge';
78
+ const result1 = SafeArray.isArray(value1);
79
+ const result2 = Array.isArray(value2);
80
+ `,
81
+ dedent`
82
+ import { SafeArray } from 'ts-std-forge';
83
+ const result1 = SafeArray.isArray(value1);
84
+ const result2 = SafeArray.isArray(value2);
85
+ `,
86
+ ],
80
87
  errors: [{ messageId: 'useIsArray' }, { messageId: 'useIsArray' }],
81
88
  },
82
89
  ],
@@ -4,10 +4,9 @@ import {
4
4
  type TSESTree,
5
5
  } from '@typescript-eslint/utils';
6
6
  import {
7
- buildNamedImportFix,
7
+ bindValueImports,
8
8
  getArgumentText,
9
- getImportDeclarations,
10
- getNamedImports,
9
+ hasForeignBinding,
11
10
  skipTypeWrappers,
12
11
  typeHoldsAtEveryLayer,
13
12
  } from 'eslint-rule-forge';
@@ -70,9 +69,9 @@ export const preferSafeArrayLengthGuard: TSESLint.RuleModule<
70
69
  schema: [],
71
70
  messages: {
72
71
  useIsEmpty:
73
- 'Replace `{{original}}` with `SafeArray.isEmpty({{arrayName}})` from ts-std-forge: the comparison narrows nothing, the guard narrows to `readonly []`.',
72
+ 'Replace `{{original}}` with `{{safeArray}}.isEmpty({{arrayName}})` from ts-std-forge: the comparison narrows nothing, the guard narrows to `readonly []`.',
74
73
  useIsNonEmpty:
75
- 'Replace `{{original}}` with `SafeArray.isNonEmpty({{arrayName}})` from ts-std-forge: the comparison narrows nothing, so `{{arrayName}}[0]` stays `| undefined`.',
74
+ 'Replace `{{original}}` with `{{safeArray}}.isNonEmpty({{arrayName}})` from ts-std-forge: the comparison narrows nothing, so `{{arrayName}}[0]` stays `| undefined`.',
76
75
  },
77
76
  defaultOptions: [],
78
77
  },
@@ -80,13 +79,6 @@ export const preferSafeArrayLengthGuard: TSESLint.RuleModule<
80
79
  create: (context) => {
81
80
  const sourceCode = context.sourceCode;
82
81
 
83
- const program = sourceCode.ast;
84
-
85
- const tsStdForgeImports = getImportDeclarations(
86
- program,
87
- TS_STD_FORGE_MODULE,
88
- );
89
-
90
82
  /**
91
83
  * Whether `expression` is an array or tuple. Without type information the
92
84
  * answer is no: `.length` is also a string's, a function's and a
@@ -127,39 +119,49 @@ export const preferSafeArrayLengthGuard: TSESLint.RuleModule<
127
119
  mut_nodesToFix.push({ node, ...matched });
128
120
  },
129
121
  'Program:exit': () => {
130
- const hasSafeArrayImport =
131
- getNamedImports(tsStdForgeImports).includes('SafeArray');
132
-
133
- // The import fix lives on the first report only:
134
- // `insertTextBefore(program, …)` produces overlapping ranges
135
- // otherwise and only one of them would survive a pass. A first report
136
- // silenced by an `eslint-disable` comment therefore takes the import
137
- // with it.
138
- for (const [
139
- index,
140
- { node, arrayExpression, guard },
141
- ] of mut_nodesToFix.entries()) {
122
+ if (mut_nodesToFix.length === 0) {
123
+ return;
124
+ }
125
+
126
+ const binding = bindValueImports(sourceCode, TS_STD_FORGE_MODULE, [
127
+ 'SafeArray',
128
+ ]);
129
+
130
+ const localName = binding?.localNames[0];
131
+
132
+ // Every fix brings `SafeArray` into scope itself, so that one applied
133
+ // alone (an editor's quick fix) is complete.
134
+ for (const { node, arrayExpression, guard } of mut_nodesToFix) {
142
135
  const arrayText = getArgumentText(sourceCode, arrayExpression);
143
136
 
137
+ const writable =
138
+ binding !== undefined &&
139
+ localName !== undefined &&
140
+ !hasForeignBinding(
141
+ sourceCode,
142
+ node,
143
+ localName,
144
+ TS_STD_FORGE_MODULE,
145
+ );
146
+
144
147
  context.report({
145
148
  node,
146
149
  messageId: MESSAGE_IDS[guard],
147
150
  data: {
148
151
  original: sourceCode.getText(node),
149
152
  arrayName: arrayText,
153
+ // The name the fix writes; with no fix, the export's own.
154
+ safeArray: writable ? localName : 'SafeArray',
150
155
  },
151
- fix: (fixer) => {
152
- const replacement = `SafeArray.${guard}(${arrayText})`;
153
-
154
- const importFixes =
155
- index === 0 && !hasSafeArrayImport
156
- ? buildNamedImportFix(fixer, program, TS_STD_FORGE_MODULE, [
157
- 'SafeArray',
158
- ])
159
- : [];
160
-
161
- return [...importFixes, fixer.replaceText(node, replacement)];
162
- },
156
+ fix: writable
157
+ ? (fixer) => [
158
+ ...binding.edits(fixer),
159
+ fixer.replaceText(
160
+ node,
161
+ `${localName}.${guard}(${arrayText})`,
162
+ ),
163
+ ]
164
+ : null,
163
165
  });
164
166
  }
165
167
  },
@@ -167,6 +169,15 @@ export const preferSafeArrayLengthGuard: TSESLint.RuleModule<
167
169
  },
168
170
  } as const;
169
171
 
172
+ const isLengthAccess = (
173
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
174
+ node: TSESTree.Node,
175
+ ): node is TSESTree.MemberExpression =>
176
+ node.type === AST_NODE_TYPES.MemberExpression &&
177
+ !node.computed &&
178
+ node.property.type === AST_NODE_TYPES.Identifier &&
179
+ node.property.name === 'length';
180
+
170
181
  /**
171
182
  * Parses `<array>.length <op> <literal>` (in either operand order) into the
172
183
  * array expression and the guard that replaces it, or `undefined` for anything
@@ -215,12 +226,3 @@ const matchLengthComparison = (
215
226
  ? undefined
216
227
  : { arrayExpression: lengthSide.object, guard: matched.guard };
217
228
  };
218
-
219
- const isLengthAccess = (
220
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
221
- node: TSESTree.Node,
222
- ): node is TSESTree.MemberExpression =>
223
- node.type === AST_NODE_TYPES.MemberExpression &&
224
- !node.computed &&
225
- node.property.type === AST_NODE_TYPES.Identifier &&
226
- node.property.name === 'length';
@@ -138,13 +138,22 @@ describe('prefer-safe-array-length-guard', () => {
138
138
  const a = xs.length === 0;
139
139
  const b = ys.length > 0;
140
140
  `,
141
- output: dedent`
142
- import { SafeArray } from 'ts-std-forge';
143
- declare const xs: readonly number[];
144
- declare const ys: readonly number[];
145
- const a = SafeArray.isEmpty(xs);
146
- const b = SafeArray.isNonEmpty(ys);
147
- `,
141
+ output: [
142
+ dedent`
143
+ import { SafeArray } from 'ts-std-forge';
144
+ declare const xs: readonly number[];
145
+ declare const ys: readonly number[];
146
+ const a = SafeArray.isEmpty(xs);
147
+ const b = ys.length > 0;
148
+ `,
149
+ dedent`
150
+ import { SafeArray } from 'ts-std-forge';
151
+ declare const xs: readonly number[];
152
+ declare const ys: readonly number[];
153
+ const a = SafeArray.isEmpty(xs);
154
+ const b = SafeArray.isNonEmpty(ys);
155
+ `,
156
+ ],
148
157
  errors: [{ messageId: 'useIsEmpty' }, { messageId: 'useIsNonEmpty' }],
149
158
  },
150
159
  ],
@@ -262,3 +271,134 @@ describe('prefer-safe-array-length-guard with parenthesized operands', () => {
262
271
  ],
263
272
  });
264
273
  });
274
+
275
+ describe('prefer-safe-array-length-guard and the import it adds', () => {
276
+ tester.run('prefer-safe-array-length-guard', preferSafeArrayLengthGuard, {
277
+ valid: [],
278
+ invalid: [
279
+ {
280
+ name: 'a leading directive stays first',
281
+ code: dedent`
282
+ 'use client';
283
+ declare const xs: readonly number[];
284
+ const ok = xs.length > 0;
285
+ `,
286
+ output: dedent`
287
+ 'use client';
288
+ import { SafeArray } from 'ts-std-forge';
289
+ declare const xs: readonly number[];
290
+ const ok = SafeArray.isNonEmpty(xs);
291
+ `,
292
+ errors: [{ messageId: 'useIsNonEmpty' }],
293
+ },
294
+ {
295
+ name: 'joins an existing named import from the module',
296
+ code: dedent`
297
+ import { isNonNullObject } from 'ts-std-forge';
298
+ declare const xs: readonly number[];
299
+ const ok = xs.length > 0;
300
+ `,
301
+ output: dedent`
302
+ import { isNonNullObject, SafeArray } from 'ts-std-forge';
303
+ declare const xs: readonly number[];
304
+ const ok = SafeArray.isNonEmpty(xs);
305
+ `,
306
+ errors: [{ messageId: 'useIsNonEmpty' }],
307
+ },
308
+ {
309
+ name: 'turns a type-only specifier into a value import',
310
+ code: dedent`
311
+ import { type SafeArray } from 'ts-std-forge';
312
+ declare const xs: readonly number[];
313
+ const ok = xs.length > 0;
314
+ `,
315
+ output: dedent`
316
+ import { SafeArray } from 'ts-std-forge';
317
+ declare const xs: readonly number[];
318
+ const ok = SafeArray.isNonEmpty(xs);
319
+ `,
320
+ errors: [{ messageId: 'useIsNonEmpty' }],
321
+ },
322
+ {
323
+ name: 'writes the local name of an aliased import',
324
+ code: dedent`
325
+ import { SafeArray as SA } from 'ts-std-forge';
326
+ declare const xs: readonly number[];
327
+ const ok = xs.length > 0;
328
+ `,
329
+ output: dedent`
330
+ import { SafeArray as SA } from 'ts-std-forge';
331
+ declare const xs: readonly number[];
332
+ const ok = SA.isNonEmpty(xs);
333
+ `,
334
+ errors: [
335
+ {
336
+ messageId: 'useIsNonEmpty',
337
+ data: {
338
+ original: 'xs.length > 0',
339
+ arrayName: 'xs',
340
+ safeArray: 'SA',
341
+ },
342
+ },
343
+ ],
344
+ },
345
+ {
346
+ name: 'no fix when an import type binds the name',
347
+ code: dedent`
348
+ import type { SafeArray } from 'ts-std-forge';
349
+ declare const xs: readonly number[];
350
+ const ok = xs.length > 0;
351
+ `,
352
+ output: null,
353
+ errors: [{ messageId: 'useIsNonEmpty' }],
354
+ },
355
+ {
356
+ name: 'no fix when another module binds the name',
357
+ code: dedent`
358
+ import { SafeArray } from 'other';
359
+ declare const xs: readonly number[];
360
+ const ok = xs.length > 0;
361
+ `,
362
+ output: null,
363
+ errors: [
364
+ {
365
+ messageId: 'useIsNonEmpty',
366
+ data: {
367
+ original: 'xs.length > 0',
368
+ arrayName: 'xs',
369
+ safeArray: 'SafeArray',
370
+ },
371
+ },
372
+ ],
373
+ },
374
+ {
375
+ name: 'no fix, and the export named, where a local binding takes the alias',
376
+ code: dedent`
377
+ import { SafeArray as SA } from 'ts-std-forge';
378
+ declare const xs: readonly number[];
379
+ const f = (SA: unknown) => xs.length > 0;
380
+ `,
381
+ output: null,
382
+ errors: [
383
+ {
384
+ messageId: 'useIsNonEmpty',
385
+ data: {
386
+ original: 'xs.length > 0',
387
+ arrayName: 'xs',
388
+ safeArray: 'SafeArray',
389
+ },
390
+ },
391
+ ],
392
+ },
393
+ {
394
+ name: 'no fix where a local binding takes the name',
395
+ code: dedent`
396
+ declare const xs: readonly number[];
397
+ const f = (SafeArray: unknown) => xs.length > 0;
398
+ `,
399
+ output: null,
400
+ errors: [{ messageId: 'useIsNonEmpty' }],
401
+ },
402
+ ],
403
+ });
404
+ });
@@ -147,12 +147,20 @@ describe('prefer-safe-number-parse-integer through type wrappers', () => {
147
147
  const a = (parseInt as typeof parseInt)(s);
148
148
  const b = (Number as NumberConstructor)!.parseInt(s, 10);
149
149
  `,
150
- output: dedent`
151
- import { Result, SafeNumber } from 'ts-std-forge';
152
- declare const s: string;
153
- const a = Result.unwrapOkOr(SafeNumber.parseInteger(s), Number.NaN);
154
- const b = Result.unwrapOkOr(SafeNumber.parseInteger(s), Number.NaN);
155
- `,
150
+ output: [
151
+ dedent`
152
+ import { Result, SafeNumber } from 'ts-std-forge';
153
+ declare const s: string;
154
+ const a = Result.unwrapOkOr(SafeNumber.parseInteger(s), Number.NaN);
155
+ const b = (Number as NumberConstructor)!.parseInt(s, 10);
156
+ `,
157
+ dedent`
158
+ import { Result, SafeNumber } from 'ts-std-forge';
159
+ declare const s: string;
160
+ const a = Result.unwrapOkOr(SafeNumber.parseInteger(s), Number.NaN);
161
+ const b = Result.unwrapOkOr(SafeNumber.parseInteger(s), Number.NaN);
162
+ `,
163
+ ],
156
164
  errors: [
157
165
  { messageId: 'useSafeParseInt' },
158
166
  { messageId: 'useSafeParseInt' },
@@ -118,8 +118,7 @@ describe('prefer-safe-number-parse', () => {
118
118
  const n = parseFloat(s);
119
119
  `,
120
120
  output: dedent`
121
- import { SafeNumber } from 'ts-std-forge';
122
- import { Result } from 'ts-std-forge';
121
+ import { Result, SafeNumber } from 'ts-std-forge';
123
122
  declare const s: string;
124
123
  const n = Result.unwrapOkOr(SafeNumber.parse(s), Number.NaN);
125
124
  `,
@@ -189,12 +188,20 @@ describe('prefer-safe-number-parse through type wrappers', () => {
189
188
  const a = (Number as NumberConstructor)(s);
190
189
  const b = Number!.parseFloat(s);
191
190
  `,
192
- output: dedent`
193
- import { Result, SafeNumber } from 'ts-std-forge';
194
- declare const s: string;
195
- const a = Result.unwrapOkOr(SafeNumber.parse(s), Number.NaN);
196
- const b = Result.unwrapOkOr(SafeNumber.parse(s), Number.NaN);
197
- `,
191
+ output: [
192
+ dedent`
193
+ import { Result, SafeNumber } from 'ts-std-forge';
194
+ declare const s: string;
195
+ const a = Result.unwrapOkOr(SafeNumber.parse(s), Number.NaN);
196
+ const b = Number!.parseFloat(s);
197
+ `,
198
+ dedent`
199
+ import { Result, SafeNumber } from 'ts-std-forge';
200
+ declare const s: string;
201
+ const a = Result.unwrapOkOr(SafeNumber.parse(s), Number.NaN);
202
+ const b = Result.unwrapOkOr(SafeNumber.parse(s), Number.NaN);
203
+ `,
204
+ ],
198
205
  errors: [
199
206
  { messageId: 'useSafeParseFloat' },
200
207
  { messageId: 'useSafeParseFloat' },