ts-const-value-transformer 0.4.1 → 0.5.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 +12 -0
- package/README.md +27 -1
- package/dist/createPortalTransformer.d.mts +42 -0
- package/dist/createPortalTransformer.mjs +126 -0
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +2 -1
- package/dist/loader.d.mts +1 -1
- package/dist/loader.mjs +10 -62
- package/dist/transform.d.mts +11 -0
- package/dist/transform.mjs +121 -53
- package/dist/version.d.mts +1 -1
- package/dist/version.mjs +1 -1
- package/package.json +11 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.5.0
|
|
4
|
+
|
|
5
|
+
- Add `useUndefinedSymbolForUndefinedValue` and `hoistUndefinedSymbol` options
|
|
6
|
+
- Add `createPortalTransformer` to use transformer easily
|
|
7
|
+
- Skip update process if unchanged
|
|
8
|
+
- Skip some declarations for performance
|
|
9
|
+
- Add hoisting computed property name
|
|
10
|
+
- Add test for element access
|
|
11
|
+
- Avoid hosting indexed property access
|
|
12
|
+
- Add test case for OmittedExpression and SatisfiesExpression
|
|
13
|
+
- Refactor to avoid handling unexpected expressions
|
|
14
|
+
|
|
3
15
|
## v0.4.1
|
|
4
16
|
|
|
5
17
|
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,7 @@ 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.
|
|
249
266
|
|
|
250
267
|
#### version: string
|
|
251
268
|
|
|
@@ -255,6 +272,15 @@ The version string of this package.
|
|
|
255
272
|
|
|
256
273
|
See [Transform options](#transform-options).
|
|
257
274
|
|
|
275
|
+
#### createPortalTransformer: (options?: CreatePortalTransformerOptions) => Promise<PortalTransformer>
|
|
276
|
+
|
|
277
|
+
Creates 'portal transformer', which can be used the transformer easily from the code which does not use TypeScript Compiler API.
|
|
278
|
+
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.)
|
|
279
|
+
|
|
280
|
+
`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.
|
|
281
|
+
|
|
282
|
+
If `Promise` cannot be used for some reason, use `createPortalTransformerSync` instead.
|
|
283
|
+
|
|
258
284
|
## Notice
|
|
259
285
|
|
|
260
286
|
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,126 @@
|
|
|
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 { printSourceWithMap } from './transform.mjs';
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
function createPortalTransformerImpl(options, ts) {
|
|
8
|
+
const project = options.project ?? 'tsconfig.json';
|
|
9
|
+
let ignoreFiles = options.ignoreFiles ?? [];
|
|
10
|
+
if (typeof ignoreFiles !== 'function') {
|
|
11
|
+
const a = ignoreFiles;
|
|
12
|
+
ignoreFiles = (fileName) => {
|
|
13
|
+
return a.some((t) => {
|
|
14
|
+
if (typeof t === 'string') {
|
|
15
|
+
return fileName.indexOf(t) >= 0;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
return t.test(fileName);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const cwd = options.cwd ?? process.cwd();
|
|
24
|
+
const getCurrentDirectory = () => cwd;
|
|
25
|
+
const config = ts.getParsedCommandLineOfConfigFile(project, void 0, {
|
|
26
|
+
fileExists: fs.existsSync,
|
|
27
|
+
getCurrentDirectory,
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
29
|
+
readDirectory: ts.sys.readDirectory,
|
|
30
|
+
readFile: (file) => fs.readFileSync(path.isAbsolute(file) ? file : path.join(getCurrentDirectory(), file), 'utf-8'),
|
|
31
|
+
useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
|
|
32
|
+
onUnRecoverableConfigFileDiagnostic: (diag) => {
|
|
33
|
+
throw new Error(ts.formatDiagnostics([diag], {
|
|
34
|
+
getCanonicalFileName: (f) => f,
|
|
35
|
+
getCurrentDirectory,
|
|
36
|
+
getNewLine: () => '\n',
|
|
37
|
+
}));
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
if (!config) {
|
|
41
|
+
throw new Error(`[ts-const-value-transformer] Unable to load tsconfig file (effective name = '${project}')`);
|
|
42
|
+
}
|
|
43
|
+
const program = ts.createProgram({
|
|
44
|
+
options: config.options,
|
|
45
|
+
rootNames: config.fileNames,
|
|
46
|
+
});
|
|
47
|
+
return {
|
|
48
|
+
ts,
|
|
49
|
+
program,
|
|
50
|
+
transform: (content, fileName, sourceMap, individualOptions) => {
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
52
|
+
const rawSourceMap = typeof sourceMap === 'string'
|
|
53
|
+
? JSON.parse(sourceMap)
|
|
54
|
+
: (sourceMap ?? void 0);
|
|
55
|
+
if (ignoreFiles(fileName)) {
|
|
56
|
+
return [content, rawSourceMap];
|
|
57
|
+
}
|
|
58
|
+
const sourceFile = program.getSourceFile(fileName);
|
|
59
|
+
if (!sourceFile) {
|
|
60
|
+
return [content, rawSourceMap];
|
|
61
|
+
}
|
|
62
|
+
// If input content is changed, replace it
|
|
63
|
+
if (content != null && sourceFile.getFullText() !== content) {
|
|
64
|
+
sourceFile.update(content, {
|
|
65
|
+
span: { start: 0, length: sourceFile.end },
|
|
66
|
+
newLength: content.length,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
const transformer = createTransformer(program, {
|
|
70
|
+
options: { ...options, ...individualOptions, ts },
|
|
71
|
+
});
|
|
72
|
+
const result = ts.transform(sourceFile, [transformer], program.getCompilerOptions());
|
|
73
|
+
const transformedSource = result.transformed[0];
|
|
74
|
+
// If unchanged, return base file as-is
|
|
75
|
+
if (transformedSource === sourceFile) {
|
|
76
|
+
return [content ?? sourceFile.getFullText(), rawSourceMap];
|
|
77
|
+
}
|
|
78
|
+
const printed = printSourceWithMap(transformedSource, fileName, rawSourceMap);
|
|
79
|
+
return printed;
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Creates the new portal transformer instance for the TS project.
|
|
85
|
+
* After creation, the transformation process can be performed by calling {@link PortalTransformer.transform}.
|
|
86
|
+
*/
|
|
87
|
+
export default async function createPortalTransformer(options = {}) {
|
|
88
|
+
let ts;
|
|
89
|
+
if (options.typescript != null) {
|
|
90
|
+
if (typeof options.typescript === 'string') {
|
|
91
|
+
ts = (await import(options.typescript));
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
ts = options.typescript;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else if (options.ts != null) {
|
|
98
|
+
ts = options.ts;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
ts = await import('typescript');
|
|
102
|
+
}
|
|
103
|
+
return createPortalTransformerImpl(options, ts);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Creates the new portal transformer instance for the TS project (using `require` function).
|
|
107
|
+
* After creation, the transformation process can be performed by calling {@link PortalTransformer.transform}.
|
|
108
|
+
*/
|
|
109
|
+
export function createPortalTransformerSync(options = {}) {
|
|
110
|
+
let ts;
|
|
111
|
+
if (options.typescript != null) {
|
|
112
|
+
if (typeof options.typescript === 'string') {
|
|
113
|
+
ts = require(options.typescript);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
ts = options.typescript;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (options.ts != null) {
|
|
120
|
+
ts = options.ts;
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
ts = require('typescript');
|
|
124
|
+
}
|
|
125
|
+
return createPortalTransformerImpl(options, ts);
|
|
126
|
+
}
|
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 {
|
|
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
|
|
4
|
-
|
|
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
|
|
28
|
-
if (!
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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);
|
package/dist/transform.d.mts
CHANGED
|
@@ -17,12 +17,23 @@ 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
|
}
|
|
27
38
|
export declare function transformSource(sourceFile: ts.SourceFile, program: ts.Program, context: ts.TransformationContext, options?: TransformOptions): ts.SourceFile;
|
|
28
39
|
export declare function printSource(sourceFile: ts.SourceFile): string;
|
package/dist/transform.mjs
CHANGED
|
@@ -12,77 +12,87 @@ 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 ?? [],
|
|
16
19
|
};
|
|
17
20
|
}
|
|
18
21
|
////////////////////////////////////////////////////////////////////////////////
|
|
19
22
|
export function transformSource(sourceFile, program, context, options) {
|
|
20
|
-
return visitNodeChildren(sourceFile, sourceFile, program, context, assignDefaultValues(options));
|
|
23
|
+
return visitNodeChildren(sourceFile, sourceFile, sourceFile, program, context, assignDefaultValues(options));
|
|
21
24
|
}
|
|
22
|
-
function visitNodeChildren(node, sourceFile, program, context, options) {
|
|
23
|
-
const newNode = visitNodeAndReplaceIfNeeded(node, sourceFile, program, context, options);
|
|
25
|
+
function visitNodeChildren(node, parent, sourceFile, program, context, options) {
|
|
26
|
+
const newNode = visitNodeAndReplaceIfNeeded(node, parent, sourceFile, program, context, options);
|
|
24
27
|
if (newNode[SYMBOL_ORIGINAL_NODE]) {
|
|
25
28
|
return newNode;
|
|
26
29
|
}
|
|
27
|
-
|
|
30
|
+
// skip children for satisifes expression
|
|
31
|
+
if (ts.isSatisfiesExpression(newNode)) {
|
|
32
|
+
return newNode;
|
|
33
|
+
}
|
|
34
|
+
// skip statements which would not have 'value' expressions
|
|
35
|
+
if (ts.isInterfaceDeclaration(newNode) ||
|
|
36
|
+
ts.isTypeAliasDeclaration(newNode) ||
|
|
37
|
+
// Identifies in import clause should not be parsed
|
|
38
|
+
ts.isImportDeclaration(newNode) ||
|
|
39
|
+
ts.isTypeOnlyExportDeclaration(newNode)) {
|
|
40
|
+
return newNode;
|
|
41
|
+
}
|
|
42
|
+
return ts.visitEachChild(newNode, (node) => visitNodeChildren(node, newNode, sourceFile, program, context, options), context);
|
|
28
43
|
}
|
|
29
|
-
function visitNodeAndReplaceIfNeeded(node, sourceFile, program, context, options) {
|
|
44
|
+
function visitNodeAndReplaceIfNeeded(node, parent, sourceFile, program, context, options) {
|
|
30
45
|
const ts = options.ts;
|
|
31
|
-
if (
|
|
32
|
-
|
|
46
|
+
if (ts.isCallLikeExpression(node)) {
|
|
47
|
+
if (!ts.isExpression(node) ||
|
|
48
|
+
(!options.unsafeHoistFunctionCall &&
|
|
49
|
+
(!options.hoistPureFunctionCall ||
|
|
50
|
+
!hasPureAnnotation(node, sourceFile, ts)))) {
|
|
51
|
+
return node;
|
|
52
|
+
}
|
|
33
53
|
}
|
|
34
|
-
if (ts.
|
|
35
|
-
ts.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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;
|
|
54
|
+
else if (ts.isIdentifier(node)) {
|
|
55
|
+
if (!ts.isComputedPropertyName(parent) &&
|
|
56
|
+
((!ts.isExpression(parent) &&
|
|
57
|
+
(!('initializer' in parent) || node !== parent.initializer)) ||
|
|
58
|
+
(ts.isPropertyAccessExpression(parent) && node === parent.name))) {
|
|
59
|
+
return node;
|
|
60
|
+
}
|
|
61
|
+
if (!options.hoistEnumValues && isEnumIdentifier(node, program, ts)) {
|
|
62
|
+
return node;
|
|
63
|
+
}
|
|
64
|
+
if (!options.hoistUndefinedSymbol &&
|
|
65
|
+
isUndefinedIdentifier(node, parent, program, ts)) {
|
|
66
|
+
return node;
|
|
67
|
+
}
|
|
61
68
|
}
|
|
62
|
-
if (
|
|
63
|
-
|
|
64
|
-
if (
|
|
69
|
+
else if (ts.isPropertyAccessExpression(node) ||
|
|
70
|
+
ts.isElementAccessExpression(node)) {
|
|
71
|
+
if (!isHoistablePropertyAccess(node, program, ts)) {
|
|
65
72
|
return node;
|
|
66
73
|
}
|
|
74
|
+
if ((!options.hoistProperty && !isEnumAccess(node, program, ts)) ||
|
|
75
|
+
(!options.hoistEnumValues &&
|
|
76
|
+
(isEnumAccess(node, program, ts) ||
|
|
77
|
+
(ts.isIdentifier(node) && isEnumIdentifier(node, program, ts))))) {
|
|
78
|
+
return node;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else if (ts.isAsExpression(node)) {
|
|
82
|
+
if (!options.unsafeHoistAsExpresion) {
|
|
83
|
+
return node;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
return node;
|
|
67
88
|
}
|
|
68
89
|
if (!options.hoistExternalValues &&
|
|
69
90
|
isExternalReference(node, program, options.externalNames)) {
|
|
70
91
|
return node;
|
|
71
92
|
}
|
|
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
93
|
if (!options.unsafeHoistAsExpresion &&
|
|
84
94
|
(hasAsExpression(node, context, ts) ||
|
|
85
|
-
hasParentAsExpression(
|
|
95
|
+
hasParentAsExpression(parent, context, ts))) {
|
|
86
96
|
return node;
|
|
87
97
|
}
|
|
88
98
|
if (!options.unsafeHoistWritableValues) {
|
|
@@ -96,6 +106,9 @@ function visitNodeAndReplaceIfNeeded(node, sourceFile, program, context, options
|
|
|
96
106
|
const type = typeChecker.getTypeAtLocation(node);
|
|
97
107
|
const flags = type.getFlags();
|
|
98
108
|
let newNode;
|
|
109
|
+
if (type.isUnionOrIntersection()) {
|
|
110
|
+
return node;
|
|
111
|
+
}
|
|
99
112
|
if (type.isStringLiteral()) {
|
|
100
113
|
newNode = context.factory.createStringLiteral(type.value);
|
|
101
114
|
}
|
|
@@ -121,7 +134,12 @@ function visitNodeAndReplaceIfNeeded(node, sourceFile, program, context, options
|
|
|
121
134
|
newNode = context.factory.createNull();
|
|
122
135
|
}
|
|
123
136
|
else if (flags & ts.TypeFlags.Undefined) {
|
|
124
|
-
|
|
137
|
+
if (options.useUndefinedSymbolForUndefinedValue) {
|
|
138
|
+
newNode = context.factory.createIdentifier('undefined');
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
newNode = context.factory.createVoidZero();
|
|
142
|
+
}
|
|
125
143
|
}
|
|
126
144
|
else {
|
|
127
145
|
return node;
|
|
@@ -224,7 +242,8 @@ function hasParentAsExpression(node, context, tsInstance) {
|
|
|
224
242
|
if (ts.isAsExpression(node) && !isAsConstExpression(node)) {
|
|
225
243
|
return true;
|
|
226
244
|
}
|
|
227
|
-
if (ts.isPropertyAccessExpression(node)
|
|
245
|
+
if (ts.isPropertyAccessExpression(node) ||
|
|
246
|
+
ts.isElementAccessExpression(node)) {
|
|
228
247
|
if (hasAsExpression(node.expression, context, ts)) {
|
|
229
248
|
return true;
|
|
230
249
|
}
|
|
@@ -246,6 +265,13 @@ function hasPureAnnotation(node, sourceFile, tsInstance) {
|
|
|
246
265
|
}
|
|
247
266
|
return false;
|
|
248
267
|
}
|
|
268
|
+
function getNameFromElementAccessExpression(node, typeChecker) {
|
|
269
|
+
const type = typeChecker.getTypeAtLocation(node.argumentExpression);
|
|
270
|
+
if (type.isStringLiteral() || type.isNumberLiteral()) {
|
|
271
|
+
return `${type.value}`;
|
|
272
|
+
}
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
249
275
|
function getMemberName(m, tsInstance) {
|
|
250
276
|
if (!m || !m.name) {
|
|
251
277
|
return '';
|
|
@@ -267,7 +293,12 @@ function getMemberName(m, tsInstance) {
|
|
|
267
293
|
function isReadonlyPropertyAccess(a, typeChecker, tsInstance) {
|
|
268
294
|
const ts = tsInstance;
|
|
269
295
|
const type = typeChecker.getTypeAtLocation(a.expression);
|
|
270
|
-
const memberName =
|
|
296
|
+
const memberName = ts.isPropertyAccessExpression(a)
|
|
297
|
+
? a.name.getText()
|
|
298
|
+
: getNameFromElementAccessExpression(a, typeChecker);
|
|
299
|
+
if (memberName == null) {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
271
302
|
if (type.getFlags() & ts.TypeFlags.Object) {
|
|
272
303
|
const dummyTypeNode = typeChecker.typeToTypeNode(type, a, ts.NodeBuilderFlags.NoTruncation);
|
|
273
304
|
if (dummyTypeNode && ts.isTypeLiteralNode(dummyTypeNode)) {
|
|
@@ -317,7 +348,8 @@ function isReadonlyExpression(node, program, tsInstance) {
|
|
|
317
348
|
}
|
|
318
349
|
}
|
|
319
350
|
}
|
|
320
|
-
if (ts.isPropertyAccessExpression(node)
|
|
351
|
+
if (ts.isPropertyAccessExpression(node) ||
|
|
352
|
+
ts.isElementAccessExpression(node)) {
|
|
321
353
|
if (isEnumAccess(node, program, ts)) {
|
|
322
354
|
return true;
|
|
323
355
|
}
|
|
@@ -325,6 +357,42 @@ function isReadonlyExpression(node, program, tsInstance) {
|
|
|
325
357
|
}
|
|
326
358
|
return null;
|
|
327
359
|
}
|
|
360
|
+
function isHoistablePropertyAccess(a, program, tsInstance) {
|
|
361
|
+
const ts = tsInstance;
|
|
362
|
+
const typeChecker = program.getTypeChecker();
|
|
363
|
+
const type = typeChecker.getTypeAtLocation(a.expression);
|
|
364
|
+
const memberName = ts.isPropertyAccessExpression(a)
|
|
365
|
+
? a.name.getText()
|
|
366
|
+
: getNameFromElementAccessExpression(a, typeChecker);
|
|
367
|
+
if (memberName == null) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
if (type.getFlags() & ts.TypeFlags.Object) {
|
|
371
|
+
const prop = type.getProperty(memberName);
|
|
372
|
+
// If the property access uses indexed access, `prop` will be undefined
|
|
373
|
+
if (prop) {
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
function isUndefinedIdentifier(node, parent, program, tsInstance) {
|
|
380
|
+
if (tsInstance.isPropertyAccessExpression(parent) ||
|
|
381
|
+
tsInstance.isElementAccessExpression(parent)) {
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
const typeChecker = program.getTypeChecker();
|
|
385
|
+
const type = typeChecker.getTypeAtLocation(node);
|
|
386
|
+
const sym = typeChecker.getSymbolAtLocation(node);
|
|
387
|
+
if (!sym || sym.getEscapedName().toString() !== 'undefined') {
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
if (type.isUnionOrIntersection() ||
|
|
391
|
+
!(type.getFlags() & ts.TypeFlags.Undefined)) {
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
328
396
|
////////////////////////////////////////////////////////////////////////////////
|
|
329
397
|
export function printSource(sourceFile) {
|
|
330
398
|
return printSourceImpl(sourceFile)[0];
|
package/dist/version.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: "0.
|
|
1
|
+
declare const _default: "0.5.0";
|
|
2
2
|
export default _default;
|
package/dist/version.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default '0.
|
|
1
|
+
export default '0.5.0';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-const-value-transformer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
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",
|