ts-const-value-transformer 0.4.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.5.1
4
+
5
+ - Fix missing for handling `ignoreFiles` for `createTransformer` (used from ts-loader, etc.)
6
+
7
+ ## v0.5.0
8
+
9
+ - Add `useUndefinedSymbolForUndefinedValue` and `hoistUndefinedSymbol` options
10
+ - Add `createPortalTransformer` to use transformer easily
11
+ - Skip update process if unchanged
12
+ - Skip some declarations for performance
13
+ - Add hoisting computed property name
14
+ - Add test for element access
15
+ - Avoid hosting indexed property access
16
+ - Add test case for OmittedExpression and SatisfiesExpression
17
+ - Refactor to avoid handling unexpected expressions
18
+
3
19
  ## v0.4.1
4
20
 
5
21
  Fix for not hositing some more unary expressions and parenthesized expression
package/README.md CHANGED
@@ -162,12 +162,25 @@ export interface TransformOptions {
162
162
  unsafeHoistAsExpresion?: boolean | undefined;
163
163
  /** Hoist properties/variables that can write (i.e. `let` / `var` variables or properies without `readonly`). Default is false because although the value is literal type at some point, the value may change to another literal type. */
164
164
  unsafeHoistWritableValues?: boolean | undefined;
165
+ /** Uses `undefined` symbol for `undefined` type values. Default is false and replaces to `void 0`. */
166
+ useUndefinedSymbolForUndefinedValue?: boolean | undefined;
167
+ /** Hoist `undefined` symbol to `void 0` (or `undefined` if useUndefinedSymbolForUndefinedValue is true). Default is true. */
168
+ hoistUndefinedSymbol?: boolean | undefined;
165
169
  /**
166
170
  * External names (tested with `.includes()` for string, with `.test()` for RegExp) for `hoistExternalValues` settings (If `hoistExternalValues` is not specified, this setting will be used).
167
171
  * - Path separators for input file name are always normalized to '/' internally.
168
172
  * - Default is `['/node_modules/']`.
169
173
  */
170
174
  externalNames?: ReadonlyArray<string | RegExp> | undefined;
175
+ /**
176
+ * Specifies for file name list or function to skip transformation. This option is used by webpack loader, the transformed called from ts-loader, and createPortalTransformer only.
177
+ * - For list, if the token is `string`, the transformation will be skipped if `fileName.indexOf(token) >= 0` is true.
178
+ * If the token is `RegExp`, the transformation will be skipped if `fileName.indexOf(token) >= 0` is true.
179
+ * - For function, the transformation will be skipped if `fn(fileName)` is true.
180
+ */
181
+ ignoreFiles?:
182
+ | ReadonlyArray<string | RegExp>
183
+ | ((fileName: string) => boolean);
171
184
  }
172
185
  ```
173
186
 
@@ -183,6 +196,10 @@ import {
183
196
  transformSource,
184
197
  version,
185
198
  type TransformOptions,
199
+ createPortalTransformer,
200
+ createPortalTransformerSync,
201
+ type CreatePortalTransformerOptions,
202
+ type PortalTransformer,
186
203
  } from 'ts-const-value-transformer';
187
204
  ```
188
205
 
@@ -245,7 +262,9 @@ Prints (generates) source code from `SourceFile`, along with raw source-map data
245
262
 
246
263
  #### transformSource: (sourceFile: ts.SourceFile, program: ts.Program, context: ts.TransformationContext, options?: TransformOptions) => ts.SourceFile
247
264
 
248
- Transforms the source file with TypeScript project. You don't need to call this function directly; use `createTransformer` instead.
265
+ Transforms the source file with TypeScript project. You don't need to call this function directly; use `createTransformer` or `createPortalTransformer` instead.
266
+
267
+ Note that `ignoreFiles` of `options` will be ignored for this function.
249
268
 
250
269
  #### version: string
251
270
 
@@ -255,6 +274,15 @@ The version string of this package.
255
274
 
256
275
  See [Transform options](#transform-options).
257
276
 
277
+ #### createPortalTransformer: (options?: CreatePortalTransformerOptions) => Promise<PortalTransformer>
278
+
279
+ Creates 'portal transformer', which can be used the transformer easily from the code which does not use TypeScript Compiler API.
280
+ The return object has `transform` method with signature: `(content: string, fileName: string, sourceMap?: string | RawSourceMap | null, options?: TransformOptions) => [newSource: string, newSourceMap: RawSourceMap | undefined]`. You can call to transform TypeScript source code. (Note that this API does not transpile to JavaScript; the output code is still TypeScript code.)
281
+
282
+ `CreatePortalTransformerOptions` has three optional options: `project` (path to tsconfig.json), `typescript` (package path to `typescript` or `typescript` namespace object), and `cwd` (current directory for file search). Also, `ignoreFiles` can be used.
283
+
284
+ If `Promise` cannot be used for some reason, use `createPortalTransformerSync` instead.
285
+
258
286
  ## Notice
259
287
 
260
288
  Starting from v0.4.0, `unsafeHoistWritableValues` option is introduced. Since TypeScript sometimes narrows non-constant values to literal types such as:
@@ -0,0 +1,42 @@
1
+ import type { RawSourceMap } from 'source-map';
2
+ import type * as tsNamespace from 'typescript';
3
+ import { type TransformOptions } from './transform.mjs';
4
+ export interface CreatePortalTransformerOptions extends TransformOptions {
5
+ project?: string;
6
+ typescript?: string | typeof tsNamespace;
7
+ cwd?: string;
8
+ }
9
+ export interface PortalTransformer {
10
+ /** The `typescript` namespace object */
11
+ readonly ts: typeof tsNamespace;
12
+ /** Active `Program` instance for the transformer */
13
+ readonly program: tsNamespace.Program;
14
+ /**
15
+ * Performs transformation.
16
+ * @param content Base source code. If null, uses loaded source code in the TS project.
17
+ * @param fileName Base file name (If not included in the TS project, transformation will not be performed.)
18
+ * @param sourceMap Base source map if exists
19
+ * @param options Transform options (addition to `options` passed to `createPortalTransformer`)
20
+ * @returns Tuple of new source code and source map. Source map may be undefined if source code is unchanged.
21
+ */
22
+ transform(content: string, fileName: string, sourceMap?: string | RawSourceMap | null, options?: TransformOptions): [newSource: string, newSourceMap: RawSourceMap | undefined];
23
+ /**
24
+ * Performs transformation.
25
+ * @param content Base source code. If null, uses loaded source code in the TS project.
26
+ * @param fileName Base file name (If not included in the TS project, transformation will not be performed.)
27
+ * @param sourceMap Base source map if exists
28
+ * @param options Transform options (addition to `options` passed to `createPortalTransformer`)
29
+ * @returns Tuple of new source code and source map. Source map may be undefined if source code is unchanged.
30
+ */
31
+ transform(content: string | null, fileName: string, sourceMap?: string | RawSourceMap | null, options?: TransformOptions): [newSource: string | null, newSourceMap: RawSourceMap | undefined];
32
+ }
33
+ /**
34
+ * Creates the new portal transformer instance for the TS project.
35
+ * After creation, the transformation process can be performed by calling {@link PortalTransformer.transform}.
36
+ */
37
+ export default function createPortalTransformer(options?: CreatePortalTransformerOptions): Promise<PortalTransformer>;
38
+ /**
39
+ * Creates the new portal transformer instance for the TS project (using `require` function).
40
+ * After creation, the transformation process can be performed by calling {@link PortalTransformer.transform}.
41
+ */
42
+ export declare function createPortalTransformerSync(options?: CreatePortalTransformerOptions): PortalTransformer;
@@ -0,0 +1,113 @@
1
+ import * as fs from 'fs';
2
+ import { createRequire } from 'module';
3
+ import * as path from 'path';
4
+ import createTransformer from './createTransformer.mjs';
5
+ import { getIgnoreFilesFunction, printSourceWithMap, } from './transform.mjs';
6
+ const require = createRequire(import.meta.url);
7
+ function createPortalTransformerImpl(options, ts) {
8
+ const project = options.project ?? 'tsconfig.json';
9
+ const ignoreFiles = getIgnoreFilesFunction(options.ignoreFiles);
10
+ const cwd = options.cwd ?? process.cwd();
11
+ const getCurrentDirectory = () => cwd;
12
+ const config = ts.getParsedCommandLineOfConfigFile(project, void 0, {
13
+ fileExists: fs.existsSync,
14
+ getCurrentDirectory,
15
+ // eslint-disable-next-line @typescript-eslint/unbound-method
16
+ readDirectory: ts.sys.readDirectory,
17
+ readFile: (file) => fs.readFileSync(path.isAbsolute(file) ? file : path.join(getCurrentDirectory(), file), 'utf-8'),
18
+ useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
19
+ onUnRecoverableConfigFileDiagnostic: (diag) => {
20
+ throw new Error(ts.formatDiagnostics([diag], {
21
+ getCanonicalFileName: (f) => f,
22
+ getCurrentDirectory,
23
+ getNewLine: () => '\n',
24
+ }));
25
+ },
26
+ });
27
+ if (!config) {
28
+ throw new Error(`[ts-const-value-transformer] Unable to load tsconfig file (effective name = '${project}')`);
29
+ }
30
+ const program = ts.createProgram({
31
+ options: config.options,
32
+ rootNames: config.fileNames,
33
+ });
34
+ return {
35
+ ts,
36
+ program,
37
+ transform: (content, fileName, sourceMap, individualOptions) => {
38
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
39
+ const rawSourceMap = typeof sourceMap === 'string'
40
+ ? JSON.parse(sourceMap)
41
+ : (sourceMap ?? void 0);
42
+ if (ignoreFiles(fileName)) {
43
+ return [content, rawSourceMap];
44
+ }
45
+ const sourceFile = program.getSourceFile(fileName);
46
+ if (!sourceFile) {
47
+ return [content, rawSourceMap];
48
+ }
49
+ // If input content is changed, replace it
50
+ if (content != null && sourceFile.getFullText() !== content) {
51
+ sourceFile.update(content, {
52
+ span: { start: 0, length: sourceFile.end },
53
+ newLength: content.length,
54
+ });
55
+ }
56
+ const transformer = createTransformer(program, {
57
+ options: { ...options, ...individualOptions, ts },
58
+ });
59
+ const result = ts.transform(sourceFile, [transformer], program.getCompilerOptions());
60
+ const transformedSource = result.transformed[0];
61
+ // If unchanged, return base file as-is
62
+ if (transformedSource === sourceFile) {
63
+ return [content ?? sourceFile.getFullText(), rawSourceMap];
64
+ }
65
+ const printed = printSourceWithMap(transformedSource, fileName, rawSourceMap);
66
+ return printed;
67
+ },
68
+ };
69
+ }
70
+ /**
71
+ * Creates the new portal transformer instance for the TS project.
72
+ * After creation, the transformation process can be performed by calling {@link PortalTransformer.transform}.
73
+ */
74
+ export default async function createPortalTransformer(options = {}) {
75
+ let ts;
76
+ if (options.typescript != null) {
77
+ if (typeof options.typescript === 'string') {
78
+ ts = (await import(options.typescript));
79
+ }
80
+ else {
81
+ ts = options.typescript;
82
+ }
83
+ }
84
+ else if (options.ts != null) {
85
+ ts = options.ts;
86
+ }
87
+ else {
88
+ ts = await import('typescript');
89
+ }
90
+ return createPortalTransformerImpl(options, ts);
91
+ }
92
+ /**
93
+ * Creates the new portal transformer instance for the TS project (using `require` function).
94
+ * After creation, the transformation process can be performed by calling {@link PortalTransformer.transform}.
95
+ */
96
+ export function createPortalTransformerSync(options = {}) {
97
+ let ts;
98
+ if (options.typescript != null) {
99
+ if (typeof options.typescript === 'string') {
100
+ ts = require(options.typescript);
101
+ }
102
+ else {
103
+ ts = options.typescript;
104
+ }
105
+ }
106
+ else if (options.ts != null) {
107
+ ts = options.ts;
108
+ }
109
+ else {
110
+ ts = require('typescript');
111
+ }
112
+ return createPortalTransformerImpl(options, ts);
113
+ }
@@ -1,15 +1,20 @@
1
- import { transformSource } from './transform.mjs';
1
+ import { getIgnoreFilesFunction, transformSource, } from './transform.mjs';
2
2
  export default function createTransformer(program,
3
3
  // for ttypescript and ts-patch
4
4
  config,
5
5
  // for ts-patch
6
6
  extras) {
7
+ const options = {
8
+ ...config?.options,
9
+ ...(extras?.ts && { ts: extras?.ts }),
10
+ };
11
+ const ignoreFiles = getIgnoreFilesFunction(options.ignoreFiles);
7
12
  return (context) => {
8
13
  return (sourceFile) => {
9
- return transformSource(sourceFile, program, context, {
10
- ...config?.options,
11
- ...(extras?.ts && { ts: extras?.ts }),
12
- });
14
+ if (ignoreFiles(sourceFile.fileName)) {
15
+ return sourceFile;
16
+ }
17
+ return transformSource(sourceFile, program, context, options);
13
18
  };
14
19
  };
15
20
  }
package/dist/index.d.mts CHANGED
@@ -1,4 +1,5 @@
1
+ import createPortalTransformer, { createPortalTransformerSync, type CreatePortalTransformerOptions, type PortalTransformer } from './createPortalTransformer.mjs';
1
2
  import createTransformer from './createTransformer.mjs';
2
3
  import version from './version.mjs';
3
4
  export { printSource, printSourceWithMap, transformSource, type TransformOptions, } from './transform.mjs';
4
- export { createTransformer, version };
5
+ export { createPortalTransformer, createPortalTransformerSync, createTransformer, type CreatePortalTransformerOptions, type PortalTransformer, version, };
package/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
+ import createPortalTransformer, { createPortalTransformerSync, } from './createPortalTransformer.mjs';
1
2
  import createTransformer from './createTransformer.mjs';
2
3
  import version from './version.mjs';
3
4
  export { printSource, printSourceWithMap, transformSource, } from './transform.mjs';
4
- export { createTransformer, version };
5
+ export { createPortalTransformer, createPortalTransformerSync, createTransformer, version, };
package/dist/loader.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type * as tsNamespace from 'typescript';
2
2
  import type * as webpack from 'webpack';
3
- import { type TransformOptions } from './transform.mjs';
3
+ import type { TransformOptions } from './transform.mjs';
4
4
  export interface TsConstValueTransformerLoaderOptions extends TransformOptions {
5
5
  project?: string;
6
6
  typescript?: string | typeof tsNamespace;
package/dist/loader.mjs CHANGED
@@ -1,74 +1,22 @@
1
- import * as fs from 'fs';
2
1
  import * as path from 'path';
3
- import createTransformer from './createTransformer.mjs';
4
- import { printSourceWithMap } from './transform.mjs';
5
- const programMap = new Map();
2
+ import createPortalTransformer, {} from './createPortalTransformer.mjs';
3
+ const transformerMap = new Map();
6
4
  const loader = function (content, sourceMap) {
7
5
  this.async();
8
6
  void (async () => {
9
7
  try {
10
8
  const options = this.getOptions() || {};
11
- let ts;
12
- if (options.typescript != null) {
13
- if (typeof options.typescript === 'string') {
14
- ts = (await import(options.typescript));
15
- }
16
- else {
17
- ts = options.typescript;
18
- }
19
- }
20
- else if (options.ts != null) {
21
- ts = options.ts;
22
- }
23
- else {
24
- ts = await import('typescript');
25
- }
26
9
  const project = options.project ?? 'tsconfig.json';
27
- let program = programMap.get(project);
28
- if (!program) {
29
- const getCurrentDirectory = () => path.dirname(this.resourcePath);
30
- const config = ts.getParsedCommandLineOfConfigFile(project, void 0, {
31
- fileExists: fs.existsSync,
32
- getCurrentDirectory,
33
- // eslint-disable-next-line @typescript-eslint/unbound-method
34
- readDirectory: ts.sys.readDirectory,
35
- readFile: (file) => fs.readFileSync(path.isAbsolute(file)
36
- ? file
37
- : path.join(getCurrentDirectory(), file), 'utf-8'),
38
- useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
39
- onUnRecoverableConfigFileDiagnostic: (diag) => {
40
- throw new Error(ts.formatDiagnostics([diag], {
41
- getCanonicalFileName: (f) => f,
42
- getCurrentDirectory,
43
- getNewLine: () => '\n',
44
- }));
45
- },
10
+ let transformer = transformerMap.get(project);
11
+ if (!transformer) {
12
+ transformer = await createPortalTransformer({
13
+ ...options,
14
+ cwd: path.dirname(this.resourcePath),
46
15
  });
47
- if (!config) {
48
- throw new Error(`[ts-const-value-transformer-loader] Unable to load tsconfig file (effective name = '${project}')`);
49
- }
50
- program = ts.createProgram({
51
- options: config.options,
52
- rootNames: config.fileNames,
53
- });
54
- programMap.set(project, program);
55
- }
56
- const sourceFile = program.getSourceFile(this.resource);
57
- if (!sourceFile) {
58
- throw new Error(`'${this.resource}' is not in the TypeScript project.`);
16
+ transformerMap.set(project, transformer);
59
17
  }
60
- sourceFile.update(content, {
61
- span: { start: 0, length: sourceFile.end },
62
- newLength: content.length,
63
- });
64
- const transformer = createTransformer(program, {
65
- options: { ...options, ts },
66
- });
67
- const result = ts.transform(sourceFile, [transformer], program.getCompilerOptions());
68
- const printed = printSourceWithMap(result.transformed[0], this.resource,
69
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
70
- typeof sourceMap === 'string' ? JSON.parse(sourceMap) : sourceMap);
71
- this.callback(null, printed[0], printed[1]);
18
+ const result = transformer.transform(content, this.resource, sourceMap);
19
+ this.callback(null, result[0], result[1]);
72
20
  }
73
21
  catch (e) {
74
22
  this.callback(e);
@@ -17,13 +17,25 @@ export interface TransformOptions {
17
17
  unsafeHoistAsExpresion?: boolean | undefined;
18
18
  /** Hoist properties/variables that can write (i.e. `let` / `var` variables or properies without `readonly`). Default is false because although the value is literal type at some point, the value may change to another literal type. */
19
19
  unsafeHoistWritableValues?: boolean | undefined;
20
+ /** Uses `undefined` symbol for `undefined` type values. Default is false and replaces to `void 0`. */
21
+ useUndefinedSymbolForUndefinedValue?: boolean | undefined;
22
+ /** Hoist `undefined` symbol to `void 0` (or `undefined` if {@linkcode useUndefinedSymbolForUndefinedValue} is true). Default is true. */
23
+ hoistUndefinedSymbol?: boolean | undefined;
20
24
  /**
21
25
  * External names (tested with `.includes()` for string, with `.test()` for RegExp) for `hoistExternalValues` settings (If `hoistExternalValues` is not specified, this setting will be used).
22
26
  * - Path separators for input file name are always normalized to '/' internally.
23
27
  * - Default is `['/node_modules/']`.
24
28
  */
25
29
  externalNames?: ReadonlyArray<string | RegExp> | undefined;
30
+ /**
31
+ * Specifies for file name list or function to skip transformation. This option is used by webpack loader, the transformed called from ts-loader, and {@link createPortalTransformer} only.
32
+ * - For list, if the token is `string`, the transformation will be skipped if `fileName.indexOf(token) >= 0` is true.
33
+ * If the token is `RegExp`, the transformation will be skipped if `fileName.indexOf(token) >= 0` is true.
34
+ * - For function, the transformation will be skipped if `fn(fileName)` is true.
35
+ */
36
+ ignoreFiles?: ReadonlyArray<string | RegExp> | ((fileName: string) => boolean);
26
37
  }
38
+ export declare function getIgnoreFilesFunction(ignoreFiles: TransformOptions['ignoreFiles']): (fileName: string) => boolean;
27
39
  export declare function transformSource(sourceFile: ts.SourceFile, program: ts.Program, context: ts.TransformationContext, options?: TransformOptions): ts.SourceFile;
28
40
  export declare function printSource(sourceFile: ts.SourceFile): string;
29
41
  export declare function printSourceWithMap(sourceFile: ts.SourceFile, originalSourceName: string, startOfSourceMap?: sourceMap.RawSourceMap): [string, sourceMap.RawSourceMap];
@@ -12,77 +12,107 @@ function assignDefaultValues(options = {}) {
12
12
  hoistPureFunctionCall: options.hoistPureFunctionCall ?? false,
13
13
  unsafeHoistFunctionCall: options.unsafeHoistFunctionCall ?? false,
14
14
  unsafeHoistWritableValues: options.unsafeHoistWritableValues ?? false,
15
+ useUndefinedSymbolForUndefinedValue: options.useUndefinedSymbolForUndefinedValue ?? false,
16
+ hoistUndefinedSymbol: options.hoistUndefinedSymbol ?? true,
15
17
  externalNames: options.externalNames ?? [],
18
+ ignoreFiles: options.ignoreFiles ?? [],
19
+ };
20
+ }
21
+ ////////////////////////////////////////////////////////////////////////////////
22
+ export function getIgnoreFilesFunction(ignoreFiles) {
23
+ if (!ignoreFiles) {
24
+ return () => false;
25
+ }
26
+ if (typeof ignoreFiles === 'function') {
27
+ return ignoreFiles;
28
+ }
29
+ const a = ignoreFiles;
30
+ return (fileName) => {
31
+ return a.some((t) => {
32
+ if (typeof t === 'string') {
33
+ return fileName.indexOf(t) >= 0;
34
+ }
35
+ else {
36
+ return t.test(fileName);
37
+ }
38
+ });
16
39
  };
17
40
  }
18
41
  ////////////////////////////////////////////////////////////////////////////////
19
42
  export function transformSource(sourceFile, program, context, options) {
20
- return visitNodeChildren(sourceFile, sourceFile, program, context, assignDefaultValues(options));
43
+ return visitNodeChildren(sourceFile, sourceFile, sourceFile, program, context, assignDefaultValues(options));
21
44
  }
22
- function visitNodeChildren(node, sourceFile, program, context, options) {
23
- const newNode = visitNodeAndReplaceIfNeeded(node, sourceFile, program, context, options);
45
+ function visitNodeChildren(node, parent, sourceFile, program, context, options) {
46
+ const newNode = visitNodeAndReplaceIfNeeded(node, parent, sourceFile, program, context, options);
24
47
  if (newNode[SYMBOL_ORIGINAL_NODE]) {
25
48
  return newNode;
26
49
  }
27
- return ts.visitEachChild(newNode, (node) => visitNodeChildren(node, sourceFile, program, context, options), context);
50
+ // skip children for satisifes expression
51
+ if (ts.isSatisfiesExpression(newNode)) {
52
+ return newNode;
53
+ }
54
+ // skip statements which would not have 'value' expressions
55
+ if (ts.isInterfaceDeclaration(newNode) ||
56
+ ts.isTypeAliasDeclaration(newNode) ||
57
+ // Identifies in import clause should not be parsed
58
+ ts.isImportDeclaration(newNode) ||
59
+ ts.isTypeOnlyExportDeclaration(newNode)) {
60
+ return newNode;
61
+ }
62
+ return ts.visitEachChild(newNode, (node) => visitNodeChildren(node, newNode, sourceFile, program, context, options), context);
28
63
  }
29
- function visitNodeAndReplaceIfNeeded(node, sourceFile, program, context, options) {
64
+ function visitNodeAndReplaceIfNeeded(node, parent, sourceFile, program, context, options) {
30
65
  const ts = options.ts;
31
- if (!ts.isExpression(node) && !ts.isIdentifier(node)) {
32
- return node;
66
+ if (ts.isCallLikeExpression(node)) {
67
+ if (!ts.isExpression(node) ||
68
+ (!options.unsafeHoistFunctionCall &&
69
+ (!options.hoistPureFunctionCall ||
70
+ !hasPureAnnotation(node, sourceFile, ts)))) {
71
+ return node;
72
+ }
33
73
  }
34
- if (ts.isLiteralExpression(node) ||
35
- ts.isObjectLiteralExpression(node) ||
36
- // UnaryExpression start
37
- ts.isPrefixUnaryExpression(node) ||
38
- ts.isPostfixUnaryExpression(node) ||
39
- ts.isDeleteExpression(node) ||
40
- ts.isTypeOfExpression(node) ||
41
- ts.isVoidExpression(node) ||
42
- ts.isAwaitExpression(node) ||
43
- ts.isTypeAssertionExpression(node) ||
44
- // UnaryExpression end
45
- ts.isBinaryExpression(node) ||
46
- ts.isParenthesizedExpression(node) ||
47
- ts.isConditionalExpression(node) ||
48
- ts.isVoidExpression(node) ||
49
- ts.isSpreadElement(node) ||
50
- (!options.hoistProperty &&
51
- ts.isPropertyAccessExpression(node) &&
52
- !isEnumAccess(node, program, ts)) ||
53
- (!options.hoistEnumValues &&
54
- ((ts.isPropertyAccessExpression(node) &&
55
- isEnumAccess(node, program, ts)) ||
56
- (ts.isIdentifier(node) && isEnumIdentifier(node, program, ts)))) ||
57
- node.kind === ts.SyntaxKind.TrueKeyword ||
58
- node.kind === ts.SyntaxKind.FalseKeyword ||
59
- node.kind === ts.SyntaxKind.NullKeyword) {
60
- return node;
74
+ else if (ts.isIdentifier(node)) {
75
+ if (!ts.isComputedPropertyName(parent) &&
76
+ ((!ts.isExpression(parent) &&
77
+ (!('initializer' in parent) || node !== parent.initializer)) ||
78
+ (ts.isPropertyAccessExpression(parent) && node === parent.name))) {
79
+ return node;
80
+ }
81
+ if (!options.hoistEnumValues && isEnumIdentifier(node, program, ts)) {
82
+ return node;
83
+ }
84
+ if (!options.hoistUndefinedSymbol &&
85
+ isUndefinedIdentifier(node, parent, program, ts)) {
86
+ return node;
87
+ }
88
+ }
89
+ else if (ts.isPropertyAccessExpression(node) ||
90
+ ts.isElementAccessExpression(node)) {
91
+ if (!isHoistablePropertyAccess(node, program, ts)) {
92
+ return node;
93
+ }
94
+ if ((!options.hoistProperty && !isEnumAccess(node, program, ts)) ||
95
+ (!options.hoistEnumValues &&
96
+ (isEnumAccess(node, program, ts) ||
97
+ (ts.isIdentifier(node) && isEnumIdentifier(node, program, ts))))) {
98
+ return node;
99
+ }
61
100
  }
62
- if (!options.unsafeHoistFunctionCall &&
63
- (!options.hoistPureFunctionCall || !hasPureAnnotation(node, sourceFile, ts))) {
64
- if (ts.isCallLikeExpression(node)) {
101
+ else if (ts.isAsExpression(node)) {
102
+ if (!options.unsafeHoistAsExpresion) {
65
103
  return node;
66
104
  }
67
105
  }
106
+ else {
107
+ return node;
108
+ }
68
109
  if (!options.hoistExternalValues &&
69
110
  isExternalReference(node, program, options.externalNames)) {
70
111
  return node;
71
112
  }
72
- if (ts.isIdentifier(node)) {
73
- if (
74
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
75
- !node.parent ||
76
- (!ts.isExpression(node.parent) &&
77
- (!('initializer' in node.parent) ||
78
- node !== node.parent.initializer)) ||
79
- (ts.isPropertyAccessExpression(node.parent) && node === node.parent.name)) {
80
- return node;
81
- }
82
- }
83
113
  if (!options.unsafeHoistAsExpresion &&
84
114
  (hasAsExpression(node, context, ts) ||
85
- hasParentAsExpression(node.parent, context, ts))) {
115
+ hasParentAsExpression(parent, context, ts))) {
86
116
  return node;
87
117
  }
88
118
  if (!options.unsafeHoistWritableValues) {
@@ -96,6 +126,9 @@ function visitNodeAndReplaceIfNeeded(node, sourceFile, program, context, options
96
126
  const type = typeChecker.getTypeAtLocation(node);
97
127
  const flags = type.getFlags();
98
128
  let newNode;
129
+ if (type.isUnionOrIntersection()) {
130
+ return node;
131
+ }
99
132
  if (type.isStringLiteral()) {
100
133
  newNode = context.factory.createStringLiteral(type.value);
101
134
  }
@@ -121,7 +154,12 @@ function visitNodeAndReplaceIfNeeded(node, sourceFile, program, context, options
121
154
  newNode = context.factory.createNull();
122
155
  }
123
156
  else if (flags & ts.TypeFlags.Undefined) {
124
- newNode = context.factory.createVoidZero();
157
+ if (options.useUndefinedSymbolForUndefinedValue) {
158
+ newNode = context.factory.createIdentifier('undefined');
159
+ }
160
+ else {
161
+ newNode = context.factory.createVoidZero();
162
+ }
125
163
  }
126
164
  else {
127
165
  return node;
@@ -224,7 +262,8 @@ function hasParentAsExpression(node, context, tsInstance) {
224
262
  if (ts.isAsExpression(node) && !isAsConstExpression(node)) {
225
263
  return true;
226
264
  }
227
- if (ts.isPropertyAccessExpression(node)) {
265
+ if (ts.isPropertyAccessExpression(node) ||
266
+ ts.isElementAccessExpression(node)) {
228
267
  if (hasAsExpression(node.expression, context, ts)) {
229
268
  return true;
230
269
  }
@@ -246,6 +285,13 @@ function hasPureAnnotation(node, sourceFile, tsInstance) {
246
285
  }
247
286
  return false;
248
287
  }
288
+ function getNameFromElementAccessExpression(node, typeChecker) {
289
+ const type = typeChecker.getTypeAtLocation(node.argumentExpression);
290
+ if (type.isStringLiteral() || type.isNumberLiteral()) {
291
+ return `${type.value}`;
292
+ }
293
+ return null;
294
+ }
249
295
  function getMemberName(m, tsInstance) {
250
296
  if (!m || !m.name) {
251
297
  return '';
@@ -267,7 +313,12 @@ function getMemberName(m, tsInstance) {
267
313
  function isReadonlyPropertyAccess(a, typeChecker, tsInstance) {
268
314
  const ts = tsInstance;
269
315
  const type = typeChecker.getTypeAtLocation(a.expression);
270
- const memberName = a.name.getText();
316
+ const memberName = ts.isPropertyAccessExpression(a)
317
+ ? a.name.getText()
318
+ : getNameFromElementAccessExpression(a, typeChecker);
319
+ if (memberName == null) {
320
+ return false;
321
+ }
271
322
  if (type.getFlags() & ts.TypeFlags.Object) {
272
323
  const dummyTypeNode = typeChecker.typeToTypeNode(type, a, ts.NodeBuilderFlags.NoTruncation);
273
324
  if (dummyTypeNode && ts.isTypeLiteralNode(dummyTypeNode)) {
@@ -317,7 +368,8 @@ function isReadonlyExpression(node, program, tsInstance) {
317
368
  }
318
369
  }
319
370
  }
320
- if (ts.isPropertyAccessExpression(node)) {
371
+ if (ts.isPropertyAccessExpression(node) ||
372
+ ts.isElementAccessExpression(node)) {
321
373
  if (isEnumAccess(node, program, ts)) {
322
374
  return true;
323
375
  }
@@ -325,6 +377,42 @@ function isReadonlyExpression(node, program, tsInstance) {
325
377
  }
326
378
  return null;
327
379
  }
380
+ function isHoistablePropertyAccess(a, program, tsInstance) {
381
+ const ts = tsInstance;
382
+ const typeChecker = program.getTypeChecker();
383
+ const type = typeChecker.getTypeAtLocation(a.expression);
384
+ const memberName = ts.isPropertyAccessExpression(a)
385
+ ? a.name.getText()
386
+ : getNameFromElementAccessExpression(a, typeChecker);
387
+ if (memberName == null) {
388
+ return false;
389
+ }
390
+ if (type.getFlags() & ts.TypeFlags.Object) {
391
+ const prop = type.getProperty(memberName);
392
+ // If the property access uses indexed access, `prop` will be undefined
393
+ if (prop) {
394
+ return true;
395
+ }
396
+ }
397
+ return false;
398
+ }
399
+ function isUndefinedIdentifier(node, parent, program, tsInstance) {
400
+ if (tsInstance.isPropertyAccessExpression(parent) ||
401
+ tsInstance.isElementAccessExpression(parent)) {
402
+ return false;
403
+ }
404
+ const typeChecker = program.getTypeChecker();
405
+ const type = typeChecker.getTypeAtLocation(node);
406
+ const sym = typeChecker.getSymbolAtLocation(node);
407
+ if (!sym || sym.getEscapedName().toString() !== 'undefined') {
408
+ return false;
409
+ }
410
+ if (type.isUnionOrIntersection() ||
411
+ !(type.getFlags() & ts.TypeFlags.Undefined)) {
412
+ return false;
413
+ }
414
+ return true;
415
+ }
328
416
  ////////////////////////////////////////////////////////////////////////////////
329
417
  export function printSource(sourceFile) {
330
418
  return printSourceImpl(sourceFile)[0];
@@ -1,2 +1,2 @@
1
- declare const _default: "0.4.1";
1
+ declare const _default: "0.5.1";
2
2
  export default _default;
package/dist/version.mjs CHANGED
@@ -1 +1 @@
1
- export default '0.4.1';
1
+ export default '0.5.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-const-value-transformer",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "engines": {
5
5
  "node": ">=20.19.3"
6
6
  },
@@ -23,6 +23,16 @@
23
23
  "import": "./dist/createTransformer.mjs",
24
24
  "types": "./dist/createTransformer.d.mts"
25
25
  },
26
+ "./createPortalTransformer": {
27
+ "default": "./dist/createPortalTransformer.mjs",
28
+ "import": "./dist/createPortalTransformer.mjs",
29
+ "types": "./dist/createPortalTransformer.d.mts"
30
+ },
31
+ "./createPortalTransformer.mjs": {
32
+ "default": "./dist/createPortalTransformer.mjs",
33
+ "import": "./dist/createPortalTransformer.mjs",
34
+ "types": "./dist/createPortalTransformer.d.mts"
35
+ },
26
36
  "./getCustomTransformers": {
27
37
  "default": "./dist/getCustomTransformers.mjs",
28
38
  "import": "./dist/getCustomTransformers.mjs",