ts-const-value-transformer 0.8.1 → 0.9.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.9.0
4
+
5
+ - Add `transformAndPrintSource` and `transformAndPrintSourceWithMap` to visit nodes only once, and use them in portal transformer
6
+
7
+ ## v0.8.2
8
+
9
+ - Fix to use internal functions to reduce memory usage
10
+
3
11
  ## v0.8.1
4
12
 
5
13
  - Fix to parenthesize expression with multi-line
package/README.md CHANGED
@@ -260,6 +260,17 @@ Prints (generates) source code from `SourceFile`, along with raw source-map data
260
260
  - `originalSourceName` would be the file name of `sourceFile`, but you can specify another name.
261
261
  - `startOfSourceMap` is a base source map (if original source file is a generated-content) if available.
262
262
 
263
+ #### transformAndPrintSource: (sourceFile: ts.SourceFile, program: ts.Program, context: ts.TransformationContext | undefined, options?: TransformOptions) => string
264
+
265
+ Transforms the source file with TypeScript project and prints a new source code. This acts like combination of `transformSource` and `printSource`, but performs in one loop, so if transformed AST is not necessary, this function is suitable.
266
+
267
+ #### transformAndPrintSourceWithMap: (sourceFile: ts.SourceFile, program: ts.Program, context: ts.TransformationContext | undefined, originalSourceName: string, options?: TransformOptions, startOfSourceMap?: RawSourceMap) => [string, RawSourceMap]
268
+
269
+ Transforms the source file with TypeScript project and prints a new source code. This acts like combination of `transformSource` and `printSourceWithMap`, but performs in one loop, so if transformed AST is not necessary, this function is suitable.
270
+
271
+ - `originalSourceName` would be the file name of `sourceFile`, but you can specify another name.
272
+ - `startOfSourceMap` is a base source map (if original source file is a generated-content) if available.
273
+
263
274
  #### transformSource: (sourceFile: ts.SourceFile, program: ts.Program, context: ts.TransformationContext, options?: TransformOptions) => ts.SourceFile
264
275
 
265
276
  Transforms the source file with TypeScript project. You don't need to call this function directly; use `createTransformer` or `createPortalTransformer` instead.
@@ -1,8 +1,7 @@
1
1
  import * as fs from 'fs';
2
2
  import { createRequire } from 'module';
3
3
  import * as path from 'path';
4
- import createTransformer from './createTransformer.mjs';
5
- import { getIgnoreFilesFunction, printSourceWithMap, } from './transform.mjs';
4
+ import { getIgnoreFilesFunction, transformAndPrintSourceWithMap, } from './transform.mjs';
6
5
  const require = createRequire(import.meta.url);
7
6
  function optionsToString(options) {
8
7
  return JSON.stringify(options, (key, value) => {
@@ -105,18 +104,9 @@ function createPortalTransformerImpl(options, ts) {
105
104
  });
106
105
  sourceFile.text = content;
107
106
  }
108
- const transformer = createTransformer(program, {
109
- options: { ...options, ...individualOptions, ts },
110
- });
111
- const transformResult = ts.transform(sourceFile, [transformer], program.getCompilerOptions());
112
- const transformedSource = transformResult.transformed[0];
113
- let result;
114
- // If unchanged, return base file as-is
115
- if (transformedSource === sourceFile) {
116
- result = [content ?? sourceFile.text, rawSourceMap];
117
- }
118
- else {
119
- result = printSourceWithMap(transformedSource, fileName, rawSourceMap, ts);
107
+ const result = transformAndPrintSourceWithMap(sourceFile, program, undefined, fileName, { ...options, ...individualOptions, ts }, rawSourceMap);
108
+ if (sourceFile.text === result[0]) {
109
+ result[1] = undefined;
120
110
  }
121
111
  if (cacheResult) {
122
112
  // This forces to concatenate strings into flatten one, to reduce object trees for ConsString
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import createPortalTransformer, { createPortalTransformerSync, type CreatePortalTransformerOptions, type PortalTransformer, type PortalTransformerResult, type PortalTransformerResultNonNull } from './createPortalTransformer.mjs';
2
2
  import createTransformer from './createTransformer.mjs';
3
3
  import version from './version.mjs';
4
- export { printSource, printSourceWithMap, transformSource, type TransformOptions, } from './transform.mjs';
4
+ export { printSource, printSourceWithMap, transformAndPrintSource, transformAndPrintSourceWithMap, transformSource, type TransformOptions, } from './transform.mjs';
5
5
  export { createPortalTransformer, createPortalTransformerSync, createTransformer, type CreatePortalTransformerOptions, type PortalTransformer, type PortalTransformerResult, type PortalTransformerResultNonNull, version, };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import createPortalTransformer, { createPortalTransformerSync, } from './createPortalTransformer.mjs';
2
2
  import createTransformer from './createTransformer.mjs';
3
3
  import version from './version.mjs';
4
- export { printSource, printSourceWithMap, transformSource, } from './transform.mjs';
4
+ export { printSource, printSourceWithMap, transformAndPrintSource, transformAndPrintSourceWithMap, transformSource, } from './transform.mjs';
5
5
  export { createPortalTransformer, createPortalTransformerSync, createTransformer, version, };
@@ -39,3 +39,5 @@ export declare function getIgnoreFilesFunction(ignoreFiles: TransformOptions['ig
39
39
  export declare function transformSource(sourceFile: ts.SourceFile, program: ts.Program, context: ts.TransformationContext | undefined, options?: TransformOptions): ts.SourceFile;
40
40
  export declare function printSource(sourceFile: ts.SourceFile, tsInstance?: typeof ts): string;
41
41
  export declare function printSourceWithMap(sourceFile: ts.SourceFile, originalSourceName: string, startOfSourceMap?: sourceMap.RawSourceMap, tsInstance?: typeof ts): [string, sourceMap.RawSourceMap];
42
+ export declare function transformAndPrintSource(sourceFile: ts.SourceFile, program: ts.Program, context: ts.TransformationContext | undefined, options?: TransformOptions): string;
43
+ export declare function transformAndPrintSourceWithMap(sourceFile: ts.SourceFile, program: ts.Program, context: ts.TransformationContext | undefined, originalSourceName: string, options?: TransformOptions, startOfSourceMap?: sourceMap.RawSourceMap): [string, sourceMap.RawSourceMap];
@@ -41,25 +41,44 @@ export function getIgnoreFilesFunction(ignoreFiles) {
41
41
  ////////////////////////////////////////////////////////////////////////////////
42
42
  export function transformSource(sourceFile, program, context, options) {
43
43
  const requiredOptions = assignDefaultValues(options);
44
- return requiredOptions.ts.visitEachChild(sourceFile, (node) => visitNodeChildren(node, sourceFile, sourceFile, program, requiredOptions, context), context);
44
+ const ts = requiredOptions.ts;
45
+ return ts.visitEachChild(sourceFile, (node) => visitNodeChildren(node, sourceFile, sourceFile, program, requiredOptions, context, (node) => {
46
+ // skip statements which would not have 'value' expressions
47
+ if (ts.isInterfaceDeclaration(node) ||
48
+ ts.isTypeAliasDeclaration(node) ||
49
+ // Identifies in import clause should not be parsed
50
+ ts.isImportDeclaration(node) ||
51
+ ts.isTypeOnlyExportDeclaration(node)) {
52
+ return false;
53
+ }
54
+ return true;
55
+ }), context);
45
56
  }
46
- function visitNodeChildren(node, parent, sourceFile, program, options, context) {
57
+ function visitNodeChildren(node, parent, sourceFile, program, options, context, fnVisit, visitContext, fnVisitBeforeReplace, fnVisitBeforeChild, fnVisitAfterChild, fnReplace) {
47
58
  const ts = options.ts;
48
- const newNode = visitNodeAndReplaceIfNeeded(node, parent, sourceFile, program, options, context);
49
- if (newNode[SYMBOL_ORIGINAL_NODE_DATA]) {
50
- return newNode;
59
+ if (fnVisitBeforeReplace) {
60
+ fnVisitBeforeReplace(node, parent, visitContext);
61
+ }
62
+ const newNode = visitNodeAndReplaceIfNeeded(node, parent, sourceFile, program, options, context, visitContext, fnReplace ?? 'create');
63
+ if (newNode == null) {
64
+ return node;
51
65
  }
52
- // skip statements which would not have 'value' expressions
53
- if (ts.isInterfaceDeclaration(newNode) ||
54
- ts.isTypeAliasDeclaration(newNode) ||
55
- // Identifies in import clause should not be parsed
56
- ts.isImportDeclaration(newNode) ||
57
- ts.isTypeOnlyExportDeclaration(newNode)) {
66
+ if (newNode !== node) {
58
67
  return newNode;
59
68
  }
60
- return ts.visitEachChild(newNode, (node) => visitNodeChildren(node, newNode, sourceFile, program, options, context), context);
69
+ if (!fnVisit(node, parent, visitContext)) {
70
+ return node;
71
+ }
72
+ const childVisitContext = fnVisitBeforeChild
73
+ ? fnVisitBeforeChild(node, parent, visitContext)
74
+ : visitContext;
75
+ const r = ts.visitEachChild(node, (nodeChild) => visitNodeChildren(nodeChild, node, sourceFile, program, options, context, fnVisit, childVisitContext, fnVisitBeforeReplace, fnVisitBeforeChild, fnVisitAfterChild, fnReplace), context);
76
+ if (fnVisitAfterChild) {
77
+ fnVisitAfterChild(node, parent, childVisitContext);
78
+ }
79
+ return r;
61
80
  }
62
- function visitNodeAndReplaceIfNeeded(node, parent, sourceFile, program, options, context) {
81
+ function visitNodeAndReplaceIfNeeded(node, parent, sourceFile, program, options, context, visitContext, replaceOption) {
63
82
  const ts = options.ts;
64
83
  if (ts.isCallLikeExpression(node)) {
65
84
  if (!ts.isExpression(node) ||
@@ -127,7 +146,9 @@ function visitNodeAndReplaceIfNeeded(node, parent, sourceFile, program, options,
127
146
  }
128
147
  let newSource;
129
148
  if (type.isStringLiteral()) {
130
- newNode = ts.factory.createStringLiteral(type.value);
149
+ if (replaceOption === 'create') {
150
+ newNode = ts.factory.createStringLiteral(type.value);
151
+ }
131
152
  newSource =
132
153
  // TypeScript namespace may export `function escapeNonAsciiString(s: string, quoteChar?: CharacterCodes.doubleQuote | CharacterCodes.singleQuote | CharacterCodes.backtick): string`
133
154
  'escapeNonAsciiString' in ts
@@ -138,36 +159,50 @@ function visitNodeAndReplaceIfNeeded(node, parent, sourceFile, program, options,
138
159
  }
139
160
  else if (type.isNumberLiteral()) {
140
161
  if (type.value < 0) {
141
- newNode = ts.factory.createParenthesizedExpression(ts.factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, ts.factory.createNumericLiteral(-type.value)));
162
+ if (replaceOption === 'create') {
163
+ newNode = ts.factory.createParenthesizedExpression(ts.factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, ts.factory.createNumericLiteral(-type.value)));
164
+ }
142
165
  newSource = `(-${-type.value})`;
143
166
  }
144
167
  else {
145
- newNode = ts.factory.createNumericLiteral(type.value);
168
+ if (replaceOption === 'create') {
169
+ newNode = ts.factory.createNumericLiteral(type.value);
170
+ }
146
171
  newSource = `${type.value}`;
147
172
  }
148
173
  }
149
174
  else if (flags & ts.TypeFlags.BigIntLiteral) {
150
175
  const text = typeChecker.typeToString(type);
151
- newNode = ts.factory.createBigIntLiteral(text);
176
+ if (replaceOption === 'create') {
177
+ newNode = ts.factory.createBigIntLiteral(text);
178
+ }
152
179
  newSource = text;
153
180
  }
154
181
  else if (flags & ts.TypeFlags.BooleanLiteral) {
155
182
  const text = typeChecker.typeToString(type);
156
- newNode =
157
- text === 'true' ? ts.factory.createTrue() : ts.factory.createFalse();
183
+ if (replaceOption === 'create') {
184
+ newNode =
185
+ text === 'true' ? ts.factory.createTrue() : ts.factory.createFalse();
186
+ }
158
187
  newSource = text;
159
188
  }
160
189
  else if (flags & ts.TypeFlags.Null) {
161
- newNode = ts.factory.createNull();
190
+ if (replaceOption === 'create') {
191
+ newNode = ts.factory.createNull();
192
+ }
162
193
  newSource = 'null';
163
194
  }
164
195
  else if (flags & ts.TypeFlags.Undefined) {
165
196
  if (options.useUndefinedSymbolForUndefinedValue) {
166
- newNode = ts.factory.createIdentifier('undefined');
197
+ if (replaceOption === 'create') {
198
+ newNode = ts.factory.createIdentifier('undefined');
199
+ }
167
200
  newSource = 'undefined';
168
201
  }
169
202
  else {
170
- newNode = ts.factory.createParenthesizedExpression(ts.factory.createVoidZero());
203
+ if (replaceOption === 'create') {
204
+ newNode = ts.factory.createParenthesizedExpression(ts.factory.createVoidZero());
205
+ }
171
206
  newSource = '(void 0)';
172
207
  }
173
208
  }
@@ -176,19 +211,28 @@ function visitNodeAndReplaceIfNeeded(node, parent, sourceFile, program, options,
176
211
  }
177
212
  const originalSource = node.getText(sourceFile);
178
213
  const comment = ` ${originalSource.replace(/\/\*/g, ' *').replace(/\*\//g, '* ')} `;
179
- let result = ts.addSyntheticTrailingComment(newNode, ts.SyntaxKind.MultiLineCommentTrivia, comment);
214
+ let result = newNode
215
+ ? ts.addSyntheticTrailingComment(newNode, ts.SyntaxKind.MultiLineCommentTrivia, comment)
216
+ : undefined;
180
217
  newSource = `${newSource} /*${comment}*/`;
181
218
  if (/[\r\n]/m.test(originalSource)) {
182
- result = ts.factory.createParenthesizedExpression(result);
219
+ if (result) {
220
+ result = ts.factory.createParenthesizedExpression(result);
221
+ }
183
222
  newSource = `(${newSource})`;
184
223
  }
185
- ts.setTextRange(result, node);
186
- result[SYMBOL_ORIGINAL_NODE_DATA] = [
187
- originalSource,
188
- newSource,
189
- node.pos,
190
- node.end,
191
- ];
224
+ if (result) {
225
+ ts.setTextRange(result, node);
226
+ result[SYMBOL_ORIGINAL_NODE_DATA] = [
227
+ originalSource,
228
+ newSource,
229
+ node.pos,
230
+ node.end,
231
+ ];
232
+ }
233
+ if (replaceOption !== 'create') {
234
+ replaceOption(newSource, originalSource, node.pos, node.end, visitContext);
235
+ }
192
236
  return result;
193
237
  }
194
238
  catch {
@@ -295,24 +339,6 @@ function getNameFromElementAccessExpression(node, typeChecker) {
295
339
  }
296
340
  return null;
297
341
  }
298
- function getMemberName(m, tsInstance) {
299
- if (!m || !m.name) {
300
- return '';
301
- }
302
- const name = m.name;
303
- if (tsInstance.isIdentifier(name)) {
304
- return name.escapedText;
305
- }
306
- else if (tsInstance.isPrivateIdentifier(name)) {
307
- return name.escapedText;
308
- }
309
- else if (tsInstance.isStringLiteral(name)) {
310
- return name.text;
311
- }
312
- else {
313
- return '';
314
- }
315
- }
316
342
  function isReadonlyPropertyAccess(a, typeChecker, tsInstance) {
317
343
  const ts = tsInstance;
318
344
  const type = typeChecker.getTypeAtLocation(a.expression);
@@ -323,32 +349,36 @@ function isReadonlyPropertyAccess(a, typeChecker, tsInstance) {
323
349
  return false;
324
350
  }
325
351
  if (type.getFlags() & ts.TypeFlags.Object) {
326
- const dummyTypeNode = typeChecker.typeToTypeNode(type, a, ts.NodeBuilderFlags.NoTruncation);
327
- if (dummyTypeNode && ts.isTypeLiteralNode(dummyTypeNode)) {
328
- for (let i = 0; i < dummyTypeNode.members.length; ++i) {
329
- const m = dummyTypeNode.members[i];
330
- if (m &&
331
- getMemberName(m, ts) === memberName &&
332
- ts.isPropertySignature(m)) {
333
- if (m.modifiers?.some((m) => m.kind === ts.SyntaxKind.ReadonlyKeyword)) {
334
- return true;
335
- }
352
+ const prop = type.getProperty(memberName);
353
+ if (prop) {
354
+ // Use internal but exported function to improve memory performance
355
+ if ('getCheckFlags' in ts &&
356
+ 'CheckFlags' in ts &&
357
+ ts.CheckFlags.Readonly != null) {
358
+ const checkFlags = ts.getCheckFlags(prop);
359
+ if (checkFlags & ts.CheckFlags.Readonly) {
360
+ return true;
336
361
  }
337
362
  }
338
- }
339
- const prop = type.getProperty(memberName);
340
- if (prop && prop.declarations && prop.declarations.length > 0) {
341
- const decl = prop.declarations[0];
342
- if (ts.isPropertySignature(decl) &&
343
- decl.modifiers?.some((m) => m.kind === ts.SyntaxKind.ReadonlyKeyword)) {
344
- return true;
363
+ if ('getDeclarationModifierFlagsFromSymbol' in ts) {
364
+ const modifierFlags = ts.getDeclarationModifierFlagsFromSymbol(prop);
365
+ if (modifierFlags & ts.ModifierFlags.Readonly) {
366
+ return true;
367
+ }
345
368
  }
346
- if (ts.isVariableDeclaration(decl) &&
347
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
348
- decl.parent &&
349
- ts.isVariableDeclarationList(decl.parent) &&
350
- decl.parent.flags & ts.NodeFlags.Const) {
351
- return true;
369
+ if (prop.declarations && prop.declarations.length > 0) {
370
+ const decl = prop.declarations[0];
371
+ if (ts.isPropertySignature(decl) &&
372
+ decl.modifiers?.some((m) => m.kind === ts.SyntaxKind.ReadonlyKeyword)) {
373
+ return true;
374
+ }
375
+ if (ts.isVariableDeclaration(decl) &&
376
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
377
+ decl.parent &&
378
+ ts.isVariableDeclarationList(decl.parent) &&
379
+ decl.parent.flags & ts.NodeFlags.Const) {
380
+ return true;
381
+ }
352
382
  }
353
383
  }
354
384
  }
@@ -508,3 +538,103 @@ function printNode(tsInstance, baseSource, sourceFile, node, posContext, origina
508
538
  }
509
539
  }
510
540
  }
541
+ ////////////////////////////////////////////////////////////////////////////////
542
+ export function transformAndPrintSource(sourceFile, program, context, options) {
543
+ return transformAndPrintSourceImpl(sourceFile, program, context, options)[0];
544
+ }
545
+ export function transformAndPrintSourceWithMap(sourceFile, program, context, originalSourceName, options, startOfSourceMap) {
546
+ const generator = new sourceMap.SourceMapGenerator(startOfSourceMap);
547
+ return transformAndPrintSourceImpl(sourceFile, program, context, options, originalSourceName, generator);
548
+ }
549
+ function transformAndPrintSourceImpl(sourceFile, program, context, options, originalSourceName, mapGenerator) {
550
+ const requiredOptions = assignDefaultValues(options);
551
+ if (mapGenerator) {
552
+ mapGenerator.setSourceContent(originalSourceName, sourceFile.getFullText());
553
+ }
554
+ const r = transformAndPrintNode({ pos: 0, diff: 0, lastLine: 0 }, sourceFile.getFullText(), sourceFile, program, context, requiredOptions, originalSourceName, mapGenerator);
555
+ return [r, mapGenerator?.toJSON()];
556
+ }
557
+ function transformAndPrintNode(posContext, baseSource, sourceFile, program, context, options, originalSourceName, mapGenerator) {
558
+ let output = '';
559
+ visitNodeChildren(sourceFile, sourceFile, sourceFile, program, options, context,
560
+ // fnVisit
561
+ (child, _parent, visitContext) => {
562
+ visitContext.lastChildPos = child.end;
563
+ return true;
564
+ },
565
+ // visitContext
566
+ {
567
+ headPrinted: false,
568
+ lastChildPos: 0,
569
+ },
570
+ // fnVisitBeforeReplace
571
+ (child, parent, visitContext) => {
572
+ if (!visitContext.headPrinted) {
573
+ visitContext.headPrinted = true;
574
+ if (child.pos > parent.pos) {
575
+ const text = baseSource.substring(parent.pos, child.pos);
576
+ output += text;
577
+ posContext.pos = child.pos;
578
+ }
579
+ }
580
+ else if (child.pos > visitContext.lastChildPos) {
581
+ const text = baseSource.substring(visitContext.lastChildPos, child.pos);
582
+ output += text;
583
+ posContext.pos = child.pos;
584
+ }
585
+ },
586
+ // fnVisitBeforeChild
587
+ () => {
588
+ return {
589
+ headPrinted: false,
590
+ lastChildPos: 0,
591
+ };
592
+ },
593
+ // fnVisitAfterChild
594
+ (node, _parent, childVisitContext) => {
595
+ if (!childVisitContext.headPrinted) {
596
+ output += baseSource.substring(node.pos, node.end);
597
+ posContext.pos = node.end;
598
+ }
599
+ else if (childVisitContext.lastChildPos < node.end) {
600
+ const text = baseSource.substring(childVisitContext.lastChildPos, node.end);
601
+ output += text;
602
+ posContext.pos = node.end;
603
+ }
604
+ },
605
+ // fnReplace
606
+ (newSource, originalSource, pos, end, visitContext) => {
607
+ const oldFull = baseSource.substring(pos, end);
608
+ const i = oldFull.lastIndexOf(originalSource);
609
+ const leadingUnchanged = i < 0 ? 0 : i;
610
+ const newText = i < 0
611
+ ? newSource
612
+ : oldFull.substring(0, i) +
613
+ newSource +
614
+ oldFull.substring(i + originalSource.length);
615
+ posContext.pos = pos + leadingUnchanged;
616
+ addMappingForCurrent(originalSource);
617
+ posContext.diff += newSource.length - originalSource.length;
618
+ posContext.pos += originalSource.length;
619
+ addMappingForCurrent();
620
+ posContext.pos = end;
621
+ output += newText;
622
+ visitContext.lastChildPos = end;
623
+ });
624
+ return output;
625
+ function addMappingForCurrent(name) {
626
+ const original = positionToLineAndColumn(sourceFile, posContext.pos, 0);
627
+ if (original.line !== posContext.lastLine) {
628
+ posContext.diff = 0;
629
+ posContext.lastLine = original.line;
630
+ }
631
+ if (mapGenerator) {
632
+ mapGenerator.addMapping({
633
+ original,
634
+ generated: positionToLineAndColumn(sourceFile, posContext.pos, posContext.diff),
635
+ source: originalSourceName,
636
+ name,
637
+ });
638
+ }
639
+ }
640
+ }
@@ -1,2 +1,2 @@
1
- declare const _default: "0.8.1";
1
+ declare const _default: "0.9.0";
2
2
  export default _default;
package/dist/version.mjs CHANGED
@@ -1 +1 @@
1
- export default '0.8.1';
1
+ export default '0.9.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-const-value-transformer",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "engines": {
5
5
  "node": ">=20.19.3"
6
6
  },