ts-codemod-lib 3.1.1 → 3.3.0

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 (25) hide show
  1. package/README.md +81 -0
  2. package/dist/entry-point.mjs +1 -0
  3. package/dist/entry-point.mjs.map +1 -1
  4. package/dist/functions/ast-transformers/append-as-const.d.mts +16 -0
  5. package/dist/functions/ast-transformers/append-as-const.d.mts.map +1 -1
  6. package/dist/functions/ast-transformers/append-as-const.mjs +101 -0
  7. package/dist/functions/ast-transformers/append-as-const.mjs.map +1 -1
  8. package/dist/functions/ast-transformers/enable-no-unchecked-indexed-access.d.mts +65 -0
  9. package/dist/functions/ast-transformers/enable-no-unchecked-indexed-access.d.mts.map +1 -0
  10. package/dist/functions/ast-transformers/enable-no-unchecked-indexed-access.mjs +155 -0
  11. package/dist/functions/ast-transformers/enable-no-unchecked-indexed-access.mjs.map +1 -0
  12. package/dist/functions/ast-transformers/index.d.mts +1 -0
  13. package/dist/functions/ast-transformers/index.d.mts.map +1 -1
  14. package/dist/functions/ast-transformers/index.mjs +1 -0
  15. package/dist/functions/ast-transformers/index.mjs.map +1 -1
  16. package/dist/functions/index.mjs +1 -0
  17. package/dist/functions/index.mjs.map +1 -1
  18. package/dist/index.mjs +1 -0
  19. package/dist/index.mjs.map +1 -1
  20. package/package.json +3 -3
  21. package/src/functions/ast-transformers/append-as-const.mts +251 -2
  22. package/src/functions/ast-transformers/append-as-const.test.mts +173 -1
  23. package/src/functions/ast-transformers/enable-no-unchecked-indexed-access.mts +438 -0
  24. package/src/functions/ast-transformers/enable-no-unchecked-indexed-access.test.mts +565 -0
  25. package/src/functions/ast-transformers/index.mts +1 -0
package/README.md CHANGED
@@ -52,6 +52,7 @@ Options:
52
52
  - `'avoidInFunctionArgs'`: Avoids adding `as const` inside function call arguments
53
53
  - `'all'`: Applies `as const` everywhere
54
54
  - `ignorePrefixes`: Array of string prefixes for identifiers that should not have `as const` added (default: `['mut_', '#mut_', '_mut_', 'draft']`)
55
+ - `removeAsConstForConstTypeParameters`: Whether to remove redundant `as const` assertions from call arguments whose corresponding parameter type is exactly a `const`-modified type parameter (default: `true`). Only applies when the callee resolves to a single call signature within the transformed file itself; imported callees, overloads, calls with explicit type arguments and spread arguments are left as they are.
55
56
 
56
57
  Example:
57
58
 
@@ -67,6 +68,18 @@ const arr2 = [1, 2, 3] as const;
67
68
  const obj2 = { a: 1, b: 2 } as const;
68
69
  ```
69
70
 
71
+ Example (`removeAsConstForConstTypeParameters`):
72
+
73
+ ```ts
74
+ const f = <const T>(x: T): T => x;
75
+
76
+ // Before
77
+ const a = f([1, 2] as const);
78
+
79
+ // After (the `const` type parameter already infers `readonly [1, 2]`)
80
+ const a2 = f([1, 2]);
81
+ ```
82
+
70
83
  ### 2. `convertToReadonlyTransformer`
71
84
 
72
85
  Converts TypeScript type definitions to readonly types. This transformer helps in creating more type-safe code by making types readonly where appropriate. It also normalizes nested readonly types (e.g., `Readonly<Readonly<T>>` becomes `Readonly<T>`).
@@ -165,6 +178,74 @@ type ReadonlyConfig2 = UnknownRecord;
165
178
  type Data2 = UnknownRecord;
166
179
  ```
167
180
 
181
+ ### 6. `enableNoUncheckedIndexedAccessTransformer`
182
+
183
+ Appends `!` to the index accesses that turning on
184
+ [`noUncheckedIndexedAccess`](https://www.typescriptlang.org/tsconfig/#noUncheckedIndexedAccess)
185
+ would turn into type errors, as a stopgap while the option is being enabled on
186
+ an existing codebase.
187
+
188
+ The rewrite is driven by the type checker rather than by syntax: each file is
189
+ checked twice, once with `noUncheckedIndexedAccess` off and once with it on,
190
+ and `!` is appended only where the option is what added `undefined` to the
191
+ expression's type. Consequently an index whose presence the type already
192
+ guarantees is left untouched — `[T, T][1]`, `[T, T, ...T[]][0]` and
193
+ `({ a: T })['a']` all keep their accesses as they are, as does an element type
194
+ that contained `undefined` to begin with, and so does an access a preceding
195
+ guard has already narrowed. `strictNullChecks` is turned on for both checks,
196
+ because `noUncheckedIndexedAccess` does nothing without it.
197
+
198
+ Both spellings of an index read are covered, because the option widens both:
199
+ `rec['a']` and the dotted `rec.a` of a type carrying an index signature. A
200
+ declared property is left alone either way, even on a type that also has an
201
+ index signature.
202
+
203
+ Options:
204
+
205
+ - `applyLevel`: `'all'` or `'avoidWhereUndefinedIsAllowed'` (default: `'all'`)
206
+ - `'all'`: appends `!` to every read whose type the option widens with `undefined`, so that as few type errors as possible are left behind
207
+ - `'avoidWhereUndefinedIsAllowed'`: additionally leaves the access alone where its contextual type already accepts `undefined` (e.g. `const x: number | undefined = xs[0];`, or an argument of a `(x: number | undefined) => void` parameter), which produces fewer but only load-bearing assertions
208
+
209
+ These positions are never rewritten, whichever level is selected:
210
+
211
+ - **Assignment targets**, where `!` is not valid syntax: `xs[0] = 1`, `xs[0] ??= 1`, `xs[0]++`, `--xs[0]`, `delete rec['a']`, `[xs[0]] = ys`, `for (xs[0] of ys)`.
212
+ - **Accesses that already account for `undefined`**: `xs[0]!`, `xs[0] as T`, `xs[0]?.foo`, `fns[0]?.()`, `xs[0] ?? d`, `xs[0] && d`.
213
+ - **Positions that read the value precisely to find out whether it is there**: `typeof xs[0]`, `!xs[0]`, `xs[0] === undefined`, and the condition of `if` / `while` / `do` / `for` / `?:`, and the subject of `switch` — asserting there would defeat the check (`case undefined:` stops compiling against an asserted subject).
214
+
215
+ Two things it cannot fix, which are left for a human to deal with:
216
+
217
+ - **Destructuring**, since `!` has nowhere to go in `const [head] = xs;` or `const { a } = rec;`.
218
+ - **Compound assignment**, `xs[0] += 1`, whose left-hand side is read as well as written.
219
+
220
+ Two things to know before running it:
221
+
222
+ - **Each file is transformed on its own**, as it is by every transformer in this library. A type that comes from another module does not resolve, and neither does a narrowing that depends on an imported type guard — `if (Arr.isNonEmpty(xs))` does not narrow `xs[0]` here. An unresolved type is never widened, so nothing is asserted there; a lost narrowing is the other way round, and produces an assertion the whole-program check does not need.
223
+ - **Review the diff, and let ESLint clean up after it.** An assertion the compiler can prove unnecessary is reported by [`@typescript-eslint/no-unnecessary-type-assertion`](https://typescript-eslint.io/rules/no-unnecessary-type-assertion/), whose fixer removes it, so running `eslint --fix` over the transformed files takes most of the surplus back out. What no rule can see is an assertion that makes a later check dead — `const v = xs[0]!; if (v === undefined) …` — which is why this is a stopgap rather than a fix.
224
+
225
+ Example:
226
+
227
+ ```ts
228
+ const xs: readonly number[] = [1, 2, 3];
229
+
230
+ const pair: readonly [number, string] = [1, 'a'];
231
+
232
+ const rec: ReadonlyRecord<string, number> = { a: 1 };
233
+
234
+ // Before (with `noUncheckedIndexedAccess` on, only `pair[1]` is known to be there)
235
+ const first: number | undefined = xs[0];
236
+
237
+ const entry: number | undefined = rec['a'];
238
+
239
+ const second: string = pair[1];
240
+
241
+ // After (the two unchecked reads are asserted, the guaranteed index is left alone)
242
+ const first2: number = xs[0]!;
243
+
244
+ const entry2: number = rec['a']!;
245
+
246
+ const second2: string = pair[1];
247
+ ```
248
+
168
249
  ### Disabling Transformers
169
250
 
170
251
  - Nodes on the line immediately following a `// transformer-ignore-next-line` comment will be skipped.
@@ -1,6 +1,7 @@
1
1
  export { appendAsConstTransformer } from './functions/ast-transformers/append-as-const.mjs';
2
2
  export { convertInterfaceToTypeTransformer } from './functions/ast-transformers/convert-interface-to-type.mjs';
3
3
  export { convertToReadonlyTransformer } from './functions/ast-transformers/convert-to-readonly.mjs';
4
+ export { enableNoUncheckedIndexedAccessTransformer } from './functions/ast-transformers/enable-no-unchecked-indexed-access.mjs';
4
5
  export { compareUnionIntersectionTypes } from './functions/ast-transformers/readonly-transformer-helpers/compare-union-types.mjs';
5
6
  export { invalidDeepReadonlyTypeName } from './functions/ast-transformers/readonly-transformer-helpers/constants.mjs';
6
7
  export { groupUnionIntersectionTypes } from './functions/ast-transformers/readonly-transformer-helpers/group-union-types.mjs';
@@ -1 +1 @@
1
- {"version":3,"file":"entry-point.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"entry-point.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;"}
@@ -14,6 +14,22 @@ export type AppendAsConstTransformerOptions = DeepReadonly<{
14
14
  * @default ['mut_', '#mut_', '_mut_', 'draft']
15
15
  */
16
16
  ignorePrefixes?: string[];
17
+ /**
18
+ * Whether to remove redundant `as const` assertions from call arguments
19
+ * whose corresponding parameter type is exactly a `const`-modified type
20
+ * parameter.
21
+ *
22
+ * (e.g. `f([1, 2] as const)` becomes `f([1, 2])` for
23
+ * `function f<const T>(x: T): T`, because the `const` type parameter
24
+ * already makes TypeScript infer the argument as if it were annotated
25
+ * with `as const`.)
26
+ *
27
+ * Only applies when the callee resolves to a single call signature within
28
+ * the transformed file itself; imported callees are left as they are.
29
+ *
30
+ * @default true
31
+ */
32
+ removeAsConstForConstTypeParameters?: boolean;
17
33
  debug?: boolean;
18
34
  }>;
19
35
  //# sourceMappingURL=append-as-const.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"append-as-const.d.mts","sourceRoot":"","sources":["../../../src/functions/ast-transformers/append-as-const.mts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAMlD,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAItD,eAAO,MAAM,wBAAwB,aACzB,+BAA+B,KACxC,kBAiCF,CAAC;AAEF,MAAM,MAAM,+BAA+B,GAAG,YAAY,CAAC;IACzD;;OAEG;IACH,UAAU,CAAC,EAAE,KAAK,GAAG,qBAAqB,CAAC;IAE3C;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAK1B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC,CAAC"}
1
+ {"version":3,"file":"append-as-const.d.mts","sourceRoot":"","sources":["../../../src/functions/ast-transformers/append-as-const.mts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAMlD,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAItD,eAAO,MAAM,wBAAwB,aACzB,+BAA+B,KACxC,kBAuCF,CAAC;AAEF,MAAM,MAAM,+BAA+B,GAAG,YAAY,CAAC;IACzD;;OAEG;IACH,UAAU,CAAC,EAAE,KAAK,GAAG,qBAAqB,CAAC;IAE3C;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAE1B;;;;;;;;;;;;;;OAcG;IACH,mCAAmC,CAAC,EAAE,OAAO,CAAC;IAE9C,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC,CAAC"}
@@ -13,6 +13,7 @@ const appendAsConstTransformer = (options) => {
13
13
  const optionsInternal = {
14
14
  applyLevel: options?.applyLevel ?? "avoidInFunctionArgs",
15
15
  ignoredPrefixes: ignorePrefixes,
16
+ removeAsConstForConstTypeParameters: options?.removeAsConstForConstTypeParameters ?? true,
16
17
  debugPrint: options?.debug === true ? console.debug : () => {
17
18
  },
18
19
  replaceNode: options?.debug === true ? replaceNodeWithDebugPrint : (node, newNodeText) => node.replaceWithText(newNodeText)
@@ -32,6 +33,9 @@ const appendAsConstTransformer = (options) => {
32
33
  optionsInternal
33
34
  );
34
35
  }
36
+ if (optionsInternal.removeAsConstForConstTypeParameters) {
37
+ removeAsConstInConstTypeParameterArgs(sourceAst, optionsInternal);
38
+ }
35
39
  }
36
40
  };
37
41
  };
@@ -214,6 +218,103 @@ const transformNode = (node, context, options) => {
214
218
  );
215
219
  }
216
220
  };
221
+ const removeAsConstInConstTypeParameterArgs = (sourceAst, options) => {
222
+ for (const call of sourceAst.getDescendantsOfKind(tsm.SyntaxKind.CallExpression).toReversed()) {
223
+ if (call.wasForgotten()) {
224
+ continue;
225
+ }
226
+ const args = call.getArguments();
227
+ if (!args.some(isAsConstNode)) {
228
+ continue;
229
+ }
230
+ if (hasDisableNextLineComment(call, TRANSFORMER_NAME)) {
231
+ continue;
232
+ }
233
+ const variableStatement = call.getFirstAncestorByKind(
234
+ tsm.SyntaxKind.VariableStatement
235
+ );
236
+ if (variableStatement !== void 0 && hasDisableNextLineComment(variableStatement, TRANSFORMER_NAME)) {
237
+ continue;
238
+ }
239
+ if (!Arr.isEmpty(call.getTypeArguments())) {
240
+ continue;
241
+ }
242
+ const callTarget = resolveSingleSignatureCallTarget(call);
243
+ if (callTarget === void 0) {
244
+ continue;
245
+ }
246
+ removeAsConstArgsOfCall(args, callTarget, options);
247
+ }
248
+ };
249
+ const removeAsConstArgsOfCall = (args, callTarget, options) => {
250
+ const firstSpreadIndex = args.findIndex(
251
+ (a) => a.isKind(tsm.SyntaxKind.SpreadElement)
252
+ );
253
+ const mappableArgCount = firstSpreadIndex === -1 ? args.length : firstSpreadIndex;
254
+ for (const [argumentIndex, argument] of args.entries()) {
255
+ if (argumentIndex >= mappableArgCount) {
256
+ return;
257
+ }
258
+ if (argument.wasForgotten() || !isAsConstNode(argument)) {
259
+ continue;
260
+ }
261
+ if (hasDisableNextLineComment(argument, TRANSFORMER_NAME)) {
262
+ continue;
263
+ }
264
+ const parameter = getMatchedParameter(callTarget.parameters, argumentIndex);
265
+ if (parameter === void 0 || !isBareConstTypeParameterReference(
266
+ parameter,
267
+ callTarget.constTypeParameterNames
268
+ )) {
269
+ continue;
270
+ }
271
+ options.debugPrint(
272
+ "removing redundant as const in const type parameter argument",
273
+ argument.getText()
274
+ );
275
+ options.replaceNode(argument, argument.getExpression().getText());
276
+ }
277
+ };
278
+ const resolveSingleSignatureCallTarget = (call) => {
279
+ if (!Arr.isFixedLengthArray(
280
+ 1,
281
+ call.getExpression().getType().getCallSignatures()
282
+ )) {
283
+ return void 0;
284
+ }
285
+ const declaration = call.getProject().getTypeChecker().getResolvedSignature(call)?.getDeclaration();
286
+ if (declaration === void 0 || !tsm.Node.isParametered(declaration) || !tsm.Node.isTypeParametered(declaration)) {
287
+ return void 0;
288
+ }
289
+ const constTypeParameterNames = ISet.create(
290
+ declaration.getTypeParameters().filter((tp) => tp.hasModifier(tsm.SyntaxKind.ConstKeyword)).map((tp) => tp.getName())
291
+ );
292
+ if (constTypeParameterNames.size === 0) {
293
+ return void 0;
294
+ }
295
+ return {
296
+ parameters: declaration.getParameters(),
297
+ constTypeParameterNames
298
+ };
299
+ };
300
+ const getMatchedParameter = (parameters, argumentIndex) => {
301
+ if (argumentIndex < parameters.length) {
302
+ return parameters[argumentIndex];
303
+ }
304
+ const lastParameter = parameters.at(-1);
305
+ return lastParameter?.isRestParameter() === true ? lastParameter : void 0;
306
+ };
307
+ const isBareConstTypeParameterReference = (parameter, constTypeParameterNames) => {
308
+ const typeNode = parameter.getTypeNode();
309
+ if (typeNode === void 0) {
310
+ return false;
311
+ }
312
+ if (!typeNode.isKind(tsm.SyntaxKind.TypeReference)) {
313
+ return false;
314
+ }
315
+ const typeName = typeNode.getTypeName();
316
+ return typeName.isKind(tsm.SyntaxKind.Identifier) && Arr.isEmpty(typeNode.getTypeArguments()) && constTypeParameterNames.has(typeName.getText());
317
+ };
217
318
 
218
319
  export { appendAsConstTransformer };
219
320
  //# sourceMappingURL=append-as-const.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"append-as-const.mjs","sources":["../../../src/functions/ast-transformers/append-as-const.mts"],"sourcesContent":["import { Arr, ISet } from 'ts-data-forge';\nimport * as tsm from 'ts-morph';\nimport { type DeepReadonly } from 'ts-type-forge';\nimport {\n hasDisableNextLineComment,\n isAsConstNode,\n} from '../functions/index.mjs';\nimport { replaceNodeWithDebugPrint } from '../utils/index.mjs';\nimport { type TsMorphTransformer } from './types.mjs';\n\nconst TRANSFORMER_NAME = 'append-as-const';\n\nexport const appendAsConstTransformer = (\n options?: AppendAsConstTransformerOptions,\n): TsMorphTransformer => {\n const ignorePrefixes = ISet.create(\n options?.ignorePrefixes ?? ['mut_', '#mut_', '_mut_', 'draft'],\n );\n\n const optionsInternal: AppendAsConstTransformerOptionsInternal = {\n applyLevel: options?.applyLevel ?? 'avoidInFunctionArgs',\n ignoredPrefixes: ignorePrefixes,\n\n debugPrint: options?.debug === true ? console.debug : () => {},\n replaceNode:\n options?.debug === true\n ? replaceNodeWithDebugPrint\n : (node, newNodeText) => node.replaceWithText(newNodeText),\n } as const;\n\n return {\n name: TRANSFORMER_NAME,\n transform: (sourceAst) => {\n for (const node of sourceAst.getDescendantsOfKind(\n tsm.SyntaxKind.VariableDeclaration,\n )) {\n transformNode(\n node,\n {\n isUnderConstContext: false,\n isDirectUnderConstInitializer: false,\n },\n optionsInternal,\n );\n }\n },\n };\n};\n\nexport type AppendAsConstTransformerOptions = DeepReadonly<{\n /**\n * @default \"avoidInFunctionArgs\"\n */\n applyLevel?: 'all' | 'avoidInFunctionArgs';\n\n /**\n * A mute keywords to ignore the readonly conversion.\n *\n * (e.g. `\"mut_\"`)\n *\n * @default ['mut_', '#mut_', '_mut_', 'draft']\n */\n ignorePrefixes?: string[];\n\n // TODO\n // ignoreConstTypeParameter?: boolean;\n\n debug?: boolean;\n}>;\n\ntype AppendAsConstTransformerOptionsInternal = DeepReadonly<{\n applyLevel: 'all' | 'avoidInFunctionArgs';\n ignoredPrefixes: ISet<string>;\n\n debugPrint: (...args: readonly unknown[]) => void;\n replaceNode: (node: tsm.Node, newNodeText: string) => void;\n}>;\n\ntype AsConstContext = Readonly<{\n /**\n * Whether the current node is under an `as const` context.\n *\n * (e.g. `[1, 2, {x: 3}] as const` --> `isUnderConstContext` is true for `[1, 2, {x: 3}]` and its children `{x: 3}`)\n */\n isUnderConstContext: boolean;\n\n /**\n * Whether the current node is directly under a `const` variable initializer.\n *\n * (e.g. `const foo = [1, 2, 3];` --> `isDirectUnderConstInitializer` is true for `[1, 2, 3]`)\n */\n isDirectUnderConstInitializer: boolean;\n}>;\n\nconst transformNode = (\n node: tsm.Node,\n context: AsConstContext,\n options: AppendAsConstTransformerOptionsInternal,\n): void => {\n if (node.wasForgotten()) {\n return;\n }\n\n options.debugPrint(node.getKindName(), node.getText(), { context });\n\n if (hasDisableNextLineComment(node, TRANSFORMER_NAME)) {\n options.debugPrint('skipped by disable-next-line comment');\n\n return;\n }\n\n if (\n options.applyLevel === 'avoidInFunctionArgs' &&\n tsm.Node.isCallExpression(node)\n ) {\n return;\n }\n\n if (node.isKind(tsm.SyntaxKind.VariableDeclaration)) {\n const nodeName = node.getName();\n\n // check for ignorePrefix\n if (options.ignoredPrefixes.some((p) => nodeName.startsWith(p))) {\n // Skip conversion for variable declarations with ignored prefixes\n // Example: const mut_foo: string[] = []; -> remains as is, without appending `as const`\n options.debugPrint('skipped variable declaration by ignorePrefixes');\n\n return;\n }\n\n const variableStatement = node.getVariableStatement();\n\n if (\n variableStatement !== undefined &&\n hasDisableNextLineComment(variableStatement, TRANSFORMER_NAME)\n ) {\n return;\n }\n\n const initializer = node.getInitializer();\n\n if (initializer === undefined) {\n return;\n }\n\n const declarationKindKeywords = node\n .getVariableStatement()\n ?.getDeclarationKindKeywords()\n .map((k) => k.getText());\n\n if (\n declarationKindKeywords !== undefined &&\n Arr.isFixedLengthArray(1, declarationKindKeywords)\n ) {\n transformNode(\n initializer,\n {\n isDirectUnderConstInitializer: declarationKindKeywords[0] === 'const',\n isUnderConstContext: false,\n },\n options,\n );\n\n return;\n }\n\n // const [a, b] = ...;\n // TODO: Support ignoredPrefixes in ArrayBindingPattern\n // if (ts.isArrayBindingPattern(nodeName)) {\n // // for (const [i, el] of nodeName.elements.entries())\n // }\n\n // const { x, y } = ...;\n // TODO: Support ignoredPrefixes in ObjectBindingPattern\n // if (ts.isObjectBindingPattern(nodeName)) {\n // // for (const [i, el] of nodeName.elements.entries())\n // }\n }\n\n // Skip already type asserted nodes\n if (tsm.Node.isAsExpression(node) && !isAsConstNode(node)) {\n return;\n }\n\n // pass by ([(X)] -> X)\n if (tsm.Node.isParenthesizedExpression(node)) {\n transformNode(node.getExpression(), context, options);\n\n return;\n }\n\n // pass by ([X satisfies ...] -> X)\n if (tsm.Node.isSatisfiesExpression(node)) {\n transformNode(node.getExpression(), context, options);\n\n return;\n }\n\n // pass by property initializer ([key: value] -> value)\n if (tsm.Node.isPropertyAssignment(node)) {\n const initializer = node.getInitializer();\n\n if (initializer !== undefined) {\n transformNode(initializer, context, options);\n }\n\n return;\n }\n\n // pass by arrow function body ([() => X] -> X)\n if (tsm.Node.isArrowFunction(node)) {\n const body = node.getBody();\n\n transformNode(body, context, options);\n\n return;\n }\n\n // pass by spread element ([...X] -> X)\n if (tsm.Node.isSpreadElement(node)) {\n transformNode(node.getExpression(), context, options);\n\n return;\n }\n\n if (\n node.isKind(tsm.SyntaxKind.NoSubstitutionTemplateLiteral) || // `abc`\n node.isKind(tsm.SyntaxKind.NumericLiteral) || // 123\n node.isKind(tsm.SyntaxKind.BigIntLiteral) || // 123n\n node.isKind(tsm.SyntaxKind.StringLiteral) || // 'abc'\n node.isKind(tsm.SyntaxKind.TrueKeyword) || // true\n node.isKind(tsm.SyntaxKind.FalseKeyword) // false\n ) {\n if (\n !context.isDirectUnderConstInitializer &&\n !context.isUnderConstContext\n ) {\n options.replaceNode(node, `${node.getText()} as const`);\n }\n\n return;\n }\n\n if (node.isKind(tsm.SyntaxKind.TemplateExpression)) {\n if (!context.isUnderConstContext) {\n options.replaceNode(node, `${node.getText()} as const`);\n }\n\n return;\n }\n\n if (tsm.Node.isArrayLiteralExpression(node)) {\n for (const el of node.getElements()) {\n transformNode(\n el,\n {\n isUnderConstContext: true, // [...] as const\n isDirectUnderConstInitializer: false,\n },\n options,\n );\n }\n\n if (!context.isUnderConstContext) {\n options.replaceNode(node, `${node.getText()} as const`);\n }\n\n return;\n }\n\n if (tsm.Node.isObjectLiteralExpression(node)) {\n for (const el of node.getProperties()) {\n transformNode(\n el,\n {\n isUnderConstContext: true, // {...} as const\n isDirectUnderConstInitializer: false,\n },\n options,\n );\n }\n\n if (!context.isUnderConstContext) {\n options.replaceNode(node, `${node.getText()} as const`);\n }\n\n return;\n }\n\n // `as const` node\n if (isAsConstNode(node)) {\n if (context.isDirectUnderConstInitializer) {\n // In const variable declarations, remove `as const` first and then re-append it later if needed\n\n transformNode(\n node.getExpression(),\n {\n isUnderConstContext: false,\n isDirectUnderConstInitializer: true,\n },\n options,\n );\n\n options.replaceNode(\n node,\n // The expression may be marked \"as const\"\n node.getExpression().getText(),\n ); // remove `as const`\n\n return;\n }\n\n if (context.isUnderConstContext) {\n transformNode(\n node.getExpression(),\n {\n isUnderConstContext: true,\n isDirectUnderConstInitializer: false,\n },\n options,\n );\n\n options.replaceNode(\n node,\n // The expression may be marked \"as const\"\n node.getExpression().getText(),\n ); // remove `as const`\n\n return;\n }\n\n transformNode(\n node.getExpression(),\n {\n isUnderConstContext: true,\n isDirectUnderConstInitializer: false,\n },\n options,\n );\n\n return;\n }\n\n if (tsm.Node.isConditionalExpression(node)) {\n // For conditional expressions, traverse both branches in a non-const context\n transformNode(\n node.getWhenTrue(),\n {\n isDirectUnderConstInitializer: context.isDirectUnderConstInitializer,\n isUnderConstContext: false,\n },\n options,\n );\n\n transformNode(\n node.getWhenFalse(),\n {\n isDirectUnderConstInitializer: context.isDirectUnderConstInitializer,\n isUnderConstContext: false,\n },\n options,\n );\n\n // return;\n }\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"append-as-const.mjs","sources":["../../../src/functions/ast-transformers/append-as-const.mts"],"sourcesContent":["import { Arr, ISet } from 'ts-data-forge';\nimport * as tsm from 'ts-morph';\nimport { type DeepReadonly } from 'ts-type-forge';\nimport {\n hasDisableNextLineComment,\n isAsConstNode,\n} from '../functions/index.mjs';\nimport { replaceNodeWithDebugPrint } from '../utils/index.mjs';\nimport { type TsMorphTransformer } from './types.mjs';\n\nconst TRANSFORMER_NAME = 'append-as-const';\n\nexport const appendAsConstTransformer = (\n options?: AppendAsConstTransformerOptions,\n): TsMorphTransformer => {\n const ignorePrefixes = ISet.create(\n options?.ignorePrefixes ?? ['mut_', '#mut_', '_mut_', 'draft'],\n );\n\n const optionsInternal: AppendAsConstTransformerOptionsInternal = {\n applyLevel: options?.applyLevel ?? 'avoidInFunctionArgs',\n ignoredPrefixes: ignorePrefixes,\n removeAsConstForConstTypeParameters:\n options?.removeAsConstForConstTypeParameters ?? true,\n\n debugPrint: options?.debug === true ? console.debug : () => {},\n replaceNode:\n options?.debug === true\n ? replaceNodeWithDebugPrint\n : (node, newNodeText) => node.replaceWithText(newNodeText),\n } as const;\n\n return {\n name: TRANSFORMER_NAME,\n transform: (sourceAst) => {\n for (const node of sourceAst.getDescendantsOfKind(\n tsm.SyntaxKind.VariableDeclaration,\n )) {\n transformNode(\n node,\n {\n isUnderConstContext: false,\n isDirectUnderConstInitializer: false,\n },\n optionsInternal,\n );\n }\n\n if (optionsInternal.removeAsConstForConstTypeParameters) {\n removeAsConstInConstTypeParameterArgs(sourceAst, optionsInternal);\n }\n },\n };\n};\n\nexport type AppendAsConstTransformerOptions = DeepReadonly<{\n /**\n * @default \"avoidInFunctionArgs\"\n */\n applyLevel?: 'all' | 'avoidInFunctionArgs';\n\n /**\n * A mute keywords to ignore the readonly conversion.\n *\n * (e.g. `\"mut_\"`)\n *\n * @default ['mut_', '#mut_', '_mut_', 'draft']\n */\n ignorePrefixes?: string[];\n\n /**\n * Whether to remove redundant `as const` assertions from call arguments\n * whose corresponding parameter type is exactly a `const`-modified type\n * parameter.\n *\n * (e.g. `f([1, 2] as const)` becomes `f([1, 2])` for\n * `function f<const T>(x: T): T`, because the `const` type parameter\n * already makes TypeScript infer the argument as if it were annotated\n * with `as const`.)\n *\n * Only applies when the callee resolves to a single call signature within\n * the transformed file itself; imported callees are left as they are.\n *\n * @default true\n */\n removeAsConstForConstTypeParameters?: boolean;\n\n debug?: boolean;\n}>;\n\ntype AppendAsConstTransformerOptionsInternal = DeepReadonly<{\n applyLevel: 'all' | 'avoidInFunctionArgs';\n ignoredPrefixes: ISet<string>;\n removeAsConstForConstTypeParameters: boolean;\n\n debugPrint: (...args: readonly unknown[]) => void;\n replaceNode: (node: tsm.Node, newNodeText: string) => void;\n}>;\n\ntype AsConstContext = Readonly<{\n /**\n * Whether the current node is under an `as const` context.\n *\n * (e.g. `[1, 2, {x: 3}] as const` --> `isUnderConstContext` is true for `[1, 2, {x: 3}]` and its children `{x: 3}`)\n */\n isUnderConstContext: boolean;\n\n /**\n * Whether the current node is directly under a `const` variable initializer.\n *\n * (e.g. `const foo = [1, 2, 3];` --> `isDirectUnderConstInitializer` is true for `[1, 2, 3]`)\n */\n isDirectUnderConstInitializer: boolean;\n}>;\n\nconst transformNode = (\n node: tsm.Node,\n context: AsConstContext,\n options: AppendAsConstTransformerOptionsInternal,\n): void => {\n if (node.wasForgotten()) {\n return;\n }\n\n options.debugPrint(node.getKindName(), node.getText(), { context });\n\n if (hasDisableNextLineComment(node, TRANSFORMER_NAME)) {\n options.debugPrint('skipped by disable-next-line comment');\n\n return;\n }\n\n if (\n options.applyLevel === 'avoidInFunctionArgs' &&\n tsm.Node.isCallExpression(node)\n ) {\n return;\n }\n\n if (node.isKind(tsm.SyntaxKind.VariableDeclaration)) {\n const nodeName = node.getName();\n\n // check for ignorePrefix\n if (options.ignoredPrefixes.some((p) => nodeName.startsWith(p))) {\n // Skip conversion for variable declarations with ignored prefixes\n // Example: const mut_foo: string[] = []; -> remains as is, without appending `as const`\n options.debugPrint('skipped variable declaration by ignorePrefixes');\n\n return;\n }\n\n const variableStatement = node.getVariableStatement();\n\n if (\n variableStatement !== undefined &&\n hasDisableNextLineComment(variableStatement, TRANSFORMER_NAME)\n ) {\n return;\n }\n\n const initializer = node.getInitializer();\n\n if (initializer === undefined) {\n return;\n }\n\n const declarationKindKeywords = node\n .getVariableStatement()\n ?.getDeclarationKindKeywords()\n .map((k) => k.getText());\n\n if (\n declarationKindKeywords !== undefined &&\n Arr.isFixedLengthArray(1, declarationKindKeywords)\n ) {\n transformNode(\n initializer,\n {\n isDirectUnderConstInitializer: declarationKindKeywords[0] === 'const',\n isUnderConstContext: false,\n },\n options,\n );\n\n return;\n }\n\n // const [a, b] = ...;\n // TODO: Support ignoredPrefixes in ArrayBindingPattern\n // if (ts.isArrayBindingPattern(nodeName)) {\n // // for (const [i, el] of nodeName.elements.entries())\n // }\n\n // const { x, y } = ...;\n // TODO: Support ignoredPrefixes in ObjectBindingPattern\n // if (ts.isObjectBindingPattern(nodeName)) {\n // // for (const [i, el] of nodeName.elements.entries())\n // }\n }\n\n // Skip already type asserted nodes\n if (tsm.Node.isAsExpression(node) && !isAsConstNode(node)) {\n return;\n }\n\n // pass by ([(X)] -> X)\n if (tsm.Node.isParenthesizedExpression(node)) {\n transformNode(node.getExpression(), context, options);\n\n return;\n }\n\n // pass by ([X satisfies ...] -> X)\n if (tsm.Node.isSatisfiesExpression(node)) {\n transformNode(node.getExpression(), context, options);\n\n return;\n }\n\n // pass by property initializer ([key: value] -> value)\n if (tsm.Node.isPropertyAssignment(node)) {\n const initializer = node.getInitializer();\n\n if (initializer !== undefined) {\n transformNode(initializer, context, options);\n }\n\n return;\n }\n\n // pass by arrow function body ([() => X] -> X)\n if (tsm.Node.isArrowFunction(node)) {\n const body = node.getBody();\n\n transformNode(body, context, options);\n\n return;\n }\n\n // pass by spread element ([...X] -> X)\n if (tsm.Node.isSpreadElement(node)) {\n transformNode(node.getExpression(), context, options);\n\n return;\n }\n\n if (\n node.isKind(tsm.SyntaxKind.NoSubstitutionTemplateLiteral) || // `abc`\n node.isKind(tsm.SyntaxKind.NumericLiteral) || // 123\n node.isKind(tsm.SyntaxKind.BigIntLiteral) || // 123n\n node.isKind(tsm.SyntaxKind.StringLiteral) || // 'abc'\n node.isKind(tsm.SyntaxKind.TrueKeyword) || // true\n node.isKind(tsm.SyntaxKind.FalseKeyword) // false\n ) {\n if (\n !context.isDirectUnderConstInitializer &&\n !context.isUnderConstContext\n ) {\n options.replaceNode(node, `${node.getText()} as const`);\n }\n\n return;\n }\n\n if (node.isKind(tsm.SyntaxKind.TemplateExpression)) {\n if (!context.isUnderConstContext) {\n options.replaceNode(node, `${node.getText()} as const`);\n }\n\n return;\n }\n\n if (tsm.Node.isArrayLiteralExpression(node)) {\n for (const el of node.getElements()) {\n transformNode(\n el,\n {\n isUnderConstContext: true, // [...] as const\n isDirectUnderConstInitializer: false,\n },\n options,\n );\n }\n\n if (!context.isUnderConstContext) {\n options.replaceNode(node, `${node.getText()} as const`);\n }\n\n return;\n }\n\n if (tsm.Node.isObjectLiteralExpression(node)) {\n for (const el of node.getProperties()) {\n transformNode(\n el,\n {\n isUnderConstContext: true, // {...} as const\n isDirectUnderConstInitializer: false,\n },\n options,\n );\n }\n\n if (!context.isUnderConstContext) {\n options.replaceNode(node, `${node.getText()} as const`);\n }\n\n return;\n }\n\n // `as const` node\n if (isAsConstNode(node)) {\n if (context.isDirectUnderConstInitializer) {\n // In const variable declarations, remove `as const` first and then re-append it later if needed\n\n transformNode(\n node.getExpression(),\n {\n isUnderConstContext: false,\n isDirectUnderConstInitializer: true,\n },\n options,\n );\n\n options.replaceNode(\n node,\n // The expression may be marked \"as const\"\n node.getExpression().getText(),\n ); // remove `as const`\n\n return;\n }\n\n if (context.isUnderConstContext) {\n transformNode(\n node.getExpression(),\n {\n isUnderConstContext: true,\n isDirectUnderConstInitializer: false,\n },\n options,\n );\n\n options.replaceNode(\n node,\n // The expression may be marked \"as const\"\n node.getExpression().getText(),\n ); // remove `as const`\n\n return;\n }\n\n transformNode(\n node.getExpression(),\n {\n isUnderConstContext: true,\n isDirectUnderConstInitializer: false,\n },\n options,\n );\n\n return;\n }\n\n if (tsm.Node.isConditionalExpression(node)) {\n // For conditional expressions, traverse both branches in a non-const context\n transformNode(\n node.getWhenTrue(),\n {\n isDirectUnderConstInitializer: context.isDirectUnderConstInitializer,\n isUnderConstContext: false,\n },\n options,\n );\n\n transformNode(\n node.getWhenFalse(),\n {\n isDirectUnderConstInitializer: context.isDirectUnderConstInitializer,\n isUnderConstContext: false,\n },\n options,\n );\n\n // return;\n }\n};\n\n/**\n * Removes redundant `as const` assertions from call arguments whose\n * corresponding parameter type is exactly a `const`-modified type parameter\n * (e.g. `f([1, 2] as const)` for `function f<const T>(x: T): T`).\n *\n * A `const` type parameter makes TypeScript treat every inference candidate\n * as if it were annotated with `as const`, so when the parameter type is the\n * bare type parameter itself, removing the assertion cannot change the\n * inferred type. Conservative bail-outs (the `as const` is kept) when:\n *\n * - the callee does not resolve to exactly one call signature (unresolved\n * imports resolve to none, since each file is transformed in an isolated\n * single-file project; overloaded functions resolve to several),\n * - the call has explicit type arguments (inference is not involved),\n * - the argument is a spread argument or follows one (its parameter position\n * is not statically known),\n * - the parameter type is not the bare type parameter (e.g. `readonly T[]`).\n */\nconst removeAsConstInConstTypeParameterArgs = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n sourceAst: tsm.SourceFile,\n options: AppendAsConstTransformerOptionsInternal,\n): void => {\n // Reversed so that calls nested inside another call's arguments are\n // processed before the outer call replaces (and thereby forgets) them.\n for (const call of sourceAst\n .getDescendantsOfKind(tsm.SyntaxKind.CallExpression)\n .toReversed()) {\n if (call.wasForgotten()) {\n continue;\n }\n\n const args = call.getArguments();\n\n // Cheap syntactic pre-check before touching the type checker\n if (!args.some(isAsConstNode)) {\n continue;\n }\n\n if (hasDisableNextLineComment(call, TRANSFORMER_NAME)) {\n continue;\n }\n\n const variableStatement = call.getFirstAncestorByKind(\n tsm.SyntaxKind.VariableStatement,\n );\n\n if (\n variableStatement !== undefined &&\n hasDisableNextLineComment(variableStatement, TRANSFORMER_NAME)\n ) {\n continue;\n }\n\n // With explicit type arguments no inference happens, so the `as const`\n // may be load-bearing for assignability.\n if (!Arr.isEmpty(call.getTypeArguments())) {\n continue;\n }\n\n const callTarget = resolveSingleSignatureCallTarget(call);\n\n if (callTarget === undefined) {\n continue;\n }\n\n removeAsConstArgsOfCall(args, callTarget, options);\n }\n};\n\nconst removeAsConstArgsOfCall = (\n args: readonly tsm.Node[],\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n callTarget: ResolvedCallTarget,\n options: AppendAsConstTransformerOptionsInternal,\n): void => {\n // Argument positions at or after a spread argument cannot be mapped to a\n // parameter statically.\n const firstSpreadIndex = args.findIndex((a) =>\n a.isKind(tsm.SyntaxKind.SpreadElement),\n );\n\n const mappableArgCount =\n firstSpreadIndex === -1 ? args.length : firstSpreadIndex;\n\n for (const [argumentIndex, argument] of args.entries()) {\n if (argumentIndex >= mappableArgCount) {\n return;\n }\n\n if (argument.wasForgotten() || !isAsConstNode(argument)) {\n continue;\n }\n\n if (hasDisableNextLineComment(argument, TRANSFORMER_NAME)) {\n continue;\n }\n\n const parameter = getMatchedParameter(callTarget.parameters, argumentIndex);\n\n if (\n parameter === undefined ||\n !isBareConstTypeParameterReference(\n parameter,\n callTarget.constTypeParameterNames,\n )\n ) {\n continue;\n }\n\n options.debugPrint(\n 'removing redundant as const in const type parameter argument',\n argument.getText(),\n );\n\n options.replaceNode(argument, argument.getExpression().getText());\n }\n};\n\ntype ResolvedCallTarget = Readonly<{\n parameters: readonly tsm.ParameterDeclaration[];\n constTypeParameterNames: ISet<string>;\n}>;\n\n/**\n * Resolves the callee of a call expression to the declaration of its single\n * call signature, and collects the names of that declaration's\n * `const`-modified type parameters.\n *\n * Returns `undefined` when the callee does not resolve to exactly one call\n * signature, the resolved declaration has no parameters or type parameters,\n * or none of the type parameters carry the `const` modifier.\n */\nconst resolveSingleSignatureCallTarget = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n call: tsm.CallExpression,\n): ResolvedCallTarget | undefined => {\n // 0 signatures: the callee is unresolved (e.g. imported, since each file\n // is transformed in an isolated single-file project).\n // 2+ signatures: overloads, where removing `as const` could change which\n // overload is selected.\n if (\n !Arr.isFixedLengthArray(\n 1,\n call.getExpression().getType().getCallSignatures(),\n )\n ) {\n return undefined;\n }\n\n const declaration = call\n .getProject()\n .getTypeChecker()\n .getResolvedSignature(call)\n ?.getDeclaration();\n\n if (\n declaration === undefined ||\n !tsm.Node.isParametered(declaration) ||\n !tsm.Node.isTypeParametered(declaration)\n ) {\n return undefined;\n }\n\n // Only type parameters declared on the call's own signature are inferred\n // at the call site; a `const` type parameter of an enclosing declaration\n // (e.g. a generic class containing this method) is already fixed there.\n const constTypeParameterNames = ISet.create(\n declaration\n .getTypeParameters()\n .filter((tp) => tp.hasModifier(tsm.SyntaxKind.ConstKeyword))\n .map((tp) => tp.getName()),\n );\n\n if (constTypeParameterNames.size === 0) {\n return undefined;\n }\n\n return {\n parameters: declaration.getParameters(),\n constTypeParameterNames,\n } as const;\n};\n\nconst getMatchedParameter = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n parameters: readonly tsm.ParameterDeclaration[],\n argumentIndex: number,\n): tsm.ParameterDeclaration | undefined => {\n if (argumentIndex < parameters.length) {\n return parameters[argumentIndex];\n }\n\n const lastParameter = parameters.at(-1);\n\n return lastParameter?.isRestParameter() === true ? lastParameter : undefined;\n};\n\n/**\n * Whether the parameter's declared type is exactly a bare reference to one of\n * the given `const`-modified type parameters (i.e. `x: T`, not `x: readonly\n * T[]` or `x: Wrapper<T>` — there the argument expression's own type is still\n * observable, so the `as const` is kept).\n */\nconst isBareConstTypeParameterReference = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n parameter: tsm.ParameterDeclaration,\n constTypeParameterNames: ISet<string>,\n): boolean => {\n const typeNode = parameter.getTypeNode();\n\n if (typeNode === undefined) {\n return false;\n }\n\n if (!typeNode.isKind(tsm.SyntaxKind.TypeReference)) {\n return false;\n }\n\n const typeName = typeNode.getTypeName();\n\n return (\n typeName.isKind(tsm.SyntaxKind.Identifier) &&\n Arr.isEmpty(typeNode.getTypeArguments()) &&\n constTypeParameterNames.has(typeName.getText())\n );\n};\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ import { type DeepReadonly } from 'ts-type-forge';
2
+ import { type TsMorphTransformer } from './types.mjs';
3
+ /**
4
+ * Appends `!` to the index accesses that turning on the
5
+ * [`noUncheckedIndexedAccess`](https://www.typescriptlang.org/tsconfig/#noUncheckedIndexedAccess)
6
+ * compiler option would turn into type errors, as a stopgap while the option
7
+ * is being enabled.
8
+ *
9
+ * The rewrite is driven by the type checker rather than by syntax: the file is
10
+ * checked twice, once with `noUncheckedIndexedAccess` off and once with it on,
11
+ * and `!` is appended only where the option is what added `undefined` to the
12
+ * expression's type. Consequently an index whose presence the type already
13
+ * guarantees keeps its access untouched — `[T, T][1]`, `[T, T, ...T[]][0]`,
14
+ * and `({ a: T })['a']` are all left as they are, as is an element type that
15
+ * contained `undefined` to begin with.
16
+ *
17
+ * Both spellings of an index read are covered, because the option widens both:
18
+ * `rec['a']` and the dotted `rec.a` of a type carrying an index signature.
19
+ *
20
+ * `strictNullChecks` is turned on for both checks, because
21
+ * `noUncheckedIndexedAccess` does nothing without it and the project the
22
+ * transformer is handed may not have it on.
23
+ *
24
+ * The result is a stopgap, not a fix, and wants reviewing. A file transformed
25
+ * on its own — which is how {@link transformSourceCode} drives every
26
+ * transformer here — resolves neither a type from another module nor a
27
+ * narrowing that depends on an imported type guard, and the latter produces
28
+ * assertions a whole-program check does not need. Most of those are reported
29
+ * by `@typescript-eslint/no-unnecessary-type-assertion`, whose fixer removes
30
+ * them; what no rule can see is an assertion that makes a later check dead,
31
+ * as the `if` of `const v = xs[0]!; if (v === undefined) …` now is.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * declare const xs: readonly number[];
36
+ * declare const pair: readonly [number, number];
37
+ * declare const rec: Record<string, number>;
38
+ * declare const known: { a: number };
39
+ *
40
+ * xs[0].toFixed(); // -> xs[0]!.toFixed();
41
+ * rec['a'].toFixed(); // -> rec['a']!.toFixed();
42
+ * rec.a.toFixed(); // -> rec.a!.toFixed();
43
+ * pair[1].toFixed(); // -> pair[1].toFixed(); (guaranteed to exist)
44
+ * known.a.toFixed(); // -> known.a.toFixed(); (guaranteed to exist)
45
+ * ```
46
+ */
47
+ export declare const enableNoUncheckedIndexedAccessTransformer: (options?: EnableNoUncheckedIndexedAccessTransformerOptions) => TsMorphTransformer;
48
+ export type EnableNoUncheckedIndexedAccessTransformerOptions = DeepReadonly<{
49
+ /**
50
+ * How eagerly `!` is appended.
51
+ *
52
+ * - `"all"`: append it to every read whose type `noUncheckedIndexedAccess`
53
+ * widens with `undefined`, so that as few type errors as possible remain.
54
+ * - `"avoidWhereUndefinedIsAllowed"`: additionally leave the access alone
55
+ * where its contextual type already accepts `undefined` (e.g.
56
+ * `const x: number | undefined = xs[0];` or an argument of a
57
+ * `(x: number | undefined) => void` parameter), which produces fewer but
58
+ * only load-bearing assertions.
59
+ *
60
+ * @default "all"
61
+ */
62
+ applyLevel?: 'all' | 'avoidWhereUndefinedIsAllowed';
63
+ debug?: boolean;
64
+ }>;
65
+ //# sourceMappingURL=enable-no-unchecked-indexed-access.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enable-no-unchecked-indexed-access.d.mts","sourceRoot":"","sources":["../../../src/functions/ast-transformers/enable-no-unchecked-indexed-access.mts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAItD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,eAAO,MAAM,yCAAyC,aAC1C,gDAAgD,KACzD,kBAyBF,CAAC;AAEF,MAAM,MAAM,gDAAgD,GAAG,YAAY,CAAC;IAC1E;;;;;;;;;;;;OAYG;IACH,UAAU,CAAC,EAAE,KAAK,GAAG,8BAA8B,CAAC;IAEpD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC,CAAC"}
@@ -0,0 +1,155 @@
1
+ import { Arr } from 'ts-data-forge';
2
+ import * as tsm from 'ts-morph';
3
+ import { hasDisableNextLineComment } from '../functions/has-disable-next-line-comment.mjs';
4
+ import '../functions/is-atomic-type-node.mjs';
5
+
6
+ const TRANSFORMER_NAME = "enable-no-unchecked-indexed-access";
7
+ const enableNoUncheckedIndexedAccessTransformer = (options) => {
8
+ const optionsInternal = {
9
+ applyLevel: options?.applyLevel ?? "all",
10
+ debugPrint: options?.debug === true ? console.debug : () => {
11
+ }
12
+ };
13
+ return {
14
+ name: TRANSFORMER_NAME,
15
+ transform: (sourceAst) => {
16
+ const positions = collectAssertionPositions(sourceAst, optionsInternal);
17
+ if (Arr.isEmpty(positions)) {
18
+ return;
19
+ }
20
+ sourceAst.replaceText(
21
+ [0, sourceAst.getEnd()],
22
+ insertNonNullAssertions(sourceAst.getFullText(), positions)
23
+ );
24
+ }
25
+ };
26
+ };
27
+ const collectAssertionPositions = (sourceAst, options) => {
28
+ const candidates = [
29
+ ...sourceAst.getDescendantsOfKind(tsm.SyntaxKind.ElementAccessExpression),
30
+ ...sourceAst.getDescendantsOfKind(tsm.SyntaxKind.PropertyAccessExpression)
31
+ ].filter(
32
+ (node) => isAssertableReadPosition(node) && !isIgnoredByComment(node)
33
+ );
34
+ if (Arr.isEmpty(candidates)) {
35
+ return [];
36
+ }
37
+ const project = sourceAst.getProject();
38
+ const originalOptions = project.getCompilerOptions();
39
+ try {
40
+ project.compilerOptions.set({
41
+ strictNullChecks: true,
42
+ noUncheckedIndexedAccess: true
43
+ });
44
+ const widened = candidates.filter(
45
+ (node) => typeIncludesUndefined(node.getType()) && !(options.applyLevel === "avoidWhereUndefinedIsAllowed" && contextAcceptsUndefined(node))
46
+ );
47
+ if (Arr.isEmpty(widened)) {
48
+ return [];
49
+ }
50
+ project.compilerOptions.set({ noUncheckedIndexedAccess: false });
51
+ return widened.filter((node) => !typeIncludesUndefined(node.getType())).map((node) => {
52
+ options.debugPrint(`${node.getText()} -> ${node.getText()}!`);
53
+ return node.getEnd();
54
+ });
55
+ } finally {
56
+ project.compilerOptions.set({
57
+ ...originalOptions,
58
+ strictNullChecks: originalOptions.strictNullChecks,
59
+ noUncheckedIndexedAccess: originalOptions.noUncheckedIndexedAccess
60
+ });
61
+ }
62
+ };
63
+ const insertNonNullAssertions = (fullText, positions) => {
64
+ const mut_parts = [];
65
+ let mut_restStart = 0;
66
+ for (const position of positions.toSorted((a, b) => a - b)) {
67
+ mut_parts.push(fullText.slice(mut_restStart, position), "!");
68
+ mut_restStart = position;
69
+ }
70
+ mut_parts.push(fullText.slice(mut_restStart));
71
+ return mut_parts.join("");
72
+ };
73
+ const typeIncludesUndefined = (type) => type.isUndefined() || type.isUnion() && type.getUnionTypes().some((t) => t.isUndefined());
74
+ const contextAcceptsUndefined = (node) => {
75
+ const contextualType = node.getContextualType();
76
+ return contextualType !== void 0 && (contextualType.isAny() || contextualType.isUnknown() || typeIncludesUndefined(contextualType));
77
+ };
78
+ const isIgnoredByComment = (node) => {
79
+ if (hasDisableNextLineComment(node, TRANSFORMER_NAME)) {
80
+ return true;
81
+ }
82
+ const statement = node.getFirstAncestor(
83
+ (ancestor) => tsm.Node.isStatement(ancestor)
84
+ );
85
+ return statement !== void 0 && hasDisableNextLineComment(statement, TRANSFORMER_NAME);
86
+ };
87
+ const isAssertableReadPosition = (node) => {
88
+ const parent = node.getParent();
89
+ if (parent === void 0) {
90
+ return false;
91
+ }
92
+ return !isAlreadyAssertedOrGuarded(node, parent) && !isWriteTarget(node, parent) && !isTestedForAbsence(node, parent);
93
+ };
94
+ const isAlreadyAssertedOrGuarded = (node, parent) => {
95
+ if (parent.isKind(tsm.SyntaxKind.NonNullExpression) || parent.isKind(tsm.SyntaxKind.AsExpression) || parent.isKind(tsm.SyntaxKind.TypeAssertionExpression)) {
96
+ return true;
97
+ }
98
+ if (isInOptionalChain(node)) {
99
+ return true;
100
+ }
101
+ return (parent.isKind(tsm.SyntaxKind.PropertyAccessExpression) || parent.isKind(tsm.SyntaxKind.ElementAccessExpression) || parent.isKind(tsm.SyntaxKind.CallExpression)) && parent.hasQuestionDotToken() && parent.getExpression() === node;
102
+ };
103
+ const isInOptionalChain = (node) => {
104
+ if (node.isKind(tsm.SyntaxKind.PropertyAccessExpression) || node.isKind(tsm.SyntaxKind.ElementAccessExpression) || node.isKind(tsm.SyntaxKind.CallExpression)) {
105
+ return node.hasQuestionDotToken() || isInOptionalChain(node.getExpression());
106
+ }
107
+ return node.isKind(tsm.SyntaxKind.NonNullExpression) && isInOptionalChain(node.getExpression());
108
+ };
109
+ const isWriteTarget = (node, parent) => {
110
+ if (parent.isKind(tsm.SyntaxKind.DeleteExpression) || parent.isKind(tsm.SyntaxKind.PostfixUnaryExpression)) {
111
+ return true;
112
+ }
113
+ if (parent.isKind(tsm.SyntaxKind.PrefixUnaryExpression)) {
114
+ const operator = parent.getOperatorToken();
115
+ return operator === tsm.SyntaxKind.PlusPlusToken || operator === tsm.SyntaxKind.MinusMinusToken;
116
+ }
117
+ if (parent.isKind(tsm.SyntaxKind.ForOfStatement) || parent.isKind(tsm.SyntaxKind.ForInStatement)) {
118
+ return parent.getInitializer() === node;
119
+ }
120
+ return parent.isKind(tsm.SyntaxKind.BinaryExpression) && parent.getLeft() === node && isAssignmentOperatorKind(parent.getOperatorToken().getKind());
121
+ };
122
+ const isTestedForAbsence = (node, parent) => {
123
+ if (parent.isKind(tsm.SyntaxKind.TypeOfExpression)) {
124
+ return true;
125
+ }
126
+ if (parent.isKind(tsm.SyntaxKind.PrefixUnaryExpression)) {
127
+ return parent.getOperatorToken() === tsm.SyntaxKind.ExclamationToken;
128
+ }
129
+ if (parent.isKind(tsm.SyntaxKind.BinaryExpression)) {
130
+ const operator = parent.getOperatorToken().getKind();
131
+ return equalityOperatorKinds.includes(operator) || parent.getLeft() === node && shortCircuitOperatorKinds.includes(operator);
132
+ }
133
+ if (parent.isKind(tsm.SyntaxKind.IfStatement) || parent.isKind(tsm.SyntaxKind.WhileStatement) || parent.isKind(tsm.SyntaxKind.DoStatement) || parent.isKind(tsm.SyntaxKind.SwitchStatement)) {
134
+ return parent.getExpression() === node;
135
+ }
136
+ if (parent.isKind(tsm.SyntaxKind.ForStatement)) {
137
+ return parent.getCondition() === node;
138
+ }
139
+ return parent.isKind(tsm.SyntaxKind.ConditionalExpression) && parent.getCondition() === node;
140
+ };
141
+ const isAssignmentOperatorKind = (kind) => tsm.SyntaxKind.FirstAssignment <= kind && kind <= tsm.SyntaxKind.LastAssignment;
142
+ const equalityOperatorKinds = [
143
+ tsm.SyntaxKind.EqualsEqualsToken,
144
+ tsm.SyntaxKind.EqualsEqualsEqualsToken,
145
+ tsm.SyntaxKind.ExclamationEqualsToken,
146
+ tsm.SyntaxKind.ExclamationEqualsEqualsToken
147
+ ];
148
+ const shortCircuitOperatorKinds = [
149
+ tsm.SyntaxKind.QuestionQuestionToken,
150
+ tsm.SyntaxKind.AmpersandAmpersandToken,
151
+ tsm.SyntaxKind.BarBarToken
152
+ ];
153
+
154
+ export { enableNoUncheckedIndexedAccessTransformer };
155
+ //# sourceMappingURL=enable-no-unchecked-indexed-access.mjs.map