ts-const-value-transformer 0.4.0 → 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 +16 -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 -45
- 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,21 @@
|
|
|
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
|
+
|
|
15
|
+
## v0.4.1
|
|
16
|
+
|
|
17
|
+
Fix for not hositing some more unary expressions and parenthesized expression
|
|
18
|
+
|
|
3
19
|
## v0.4.0
|
|
4
20
|
|
|
5
21
|
Add `unsafeHoistWritableValues` option to prevent from hoisting non-const values with literal type unexpectedly
|
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,69 +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.isIdentifier(node) && isEnumIdentifier(node, program, ts)))) ||
|
|
49
|
-
node.kind === ts.SyntaxKind.TrueKeyword ||
|
|
50
|
-
node.kind === ts.SyntaxKind.FalseKeyword ||
|
|
51
|
-
node.kind === ts.SyntaxKind.NullKeyword) {
|
|
52
|
-
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
|
+
}
|
|
53
68
|
}
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
if (
|
|
69
|
+
else if (ts.isPropertyAccessExpression(node) ||
|
|
70
|
+
ts.isElementAccessExpression(node)) {
|
|
71
|
+
if (!isHoistablePropertyAccess(node, program, ts)) {
|
|
57
72
|
return node;
|
|
58
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;
|
|
59
88
|
}
|
|
60
89
|
if (!options.hoistExternalValues &&
|
|
61
90
|
isExternalReference(node, program, options.externalNames)) {
|
|
62
91
|
return node;
|
|
63
92
|
}
|
|
64
|
-
if (ts.isIdentifier(node)) {
|
|
65
|
-
if (
|
|
66
|
-
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
67
|
-
!node.parent ||
|
|
68
|
-
(!ts.isExpression(node.parent) &&
|
|
69
|
-
(!('initializer' in node.parent) ||
|
|
70
|
-
node !== node.parent.initializer)) ||
|
|
71
|
-
(ts.isPropertyAccessExpression(node.parent) && node === node.parent.name)) {
|
|
72
|
-
return node;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
93
|
if (!options.unsafeHoistAsExpresion &&
|
|
76
94
|
(hasAsExpression(node, context, ts) ||
|
|
77
|
-
hasParentAsExpression(
|
|
95
|
+
hasParentAsExpression(parent, context, ts))) {
|
|
78
96
|
return node;
|
|
79
97
|
}
|
|
80
98
|
if (!options.unsafeHoistWritableValues) {
|
|
@@ -88,6 +106,9 @@ function visitNodeAndReplaceIfNeeded(node, sourceFile, program, context, options
|
|
|
88
106
|
const type = typeChecker.getTypeAtLocation(node);
|
|
89
107
|
const flags = type.getFlags();
|
|
90
108
|
let newNode;
|
|
109
|
+
if (type.isUnionOrIntersection()) {
|
|
110
|
+
return node;
|
|
111
|
+
}
|
|
91
112
|
if (type.isStringLiteral()) {
|
|
92
113
|
newNode = context.factory.createStringLiteral(type.value);
|
|
93
114
|
}
|
|
@@ -113,7 +134,12 @@ function visitNodeAndReplaceIfNeeded(node, sourceFile, program, context, options
|
|
|
113
134
|
newNode = context.factory.createNull();
|
|
114
135
|
}
|
|
115
136
|
else if (flags & ts.TypeFlags.Undefined) {
|
|
116
|
-
|
|
137
|
+
if (options.useUndefinedSymbolForUndefinedValue) {
|
|
138
|
+
newNode = context.factory.createIdentifier('undefined');
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
newNode = context.factory.createVoidZero();
|
|
142
|
+
}
|
|
117
143
|
}
|
|
118
144
|
else {
|
|
119
145
|
return node;
|
|
@@ -216,7 +242,8 @@ function hasParentAsExpression(node, context, tsInstance) {
|
|
|
216
242
|
if (ts.isAsExpression(node) && !isAsConstExpression(node)) {
|
|
217
243
|
return true;
|
|
218
244
|
}
|
|
219
|
-
if (ts.isPropertyAccessExpression(node)
|
|
245
|
+
if (ts.isPropertyAccessExpression(node) ||
|
|
246
|
+
ts.isElementAccessExpression(node)) {
|
|
220
247
|
if (hasAsExpression(node.expression, context, ts)) {
|
|
221
248
|
return true;
|
|
222
249
|
}
|
|
@@ -238,6 +265,13 @@ function hasPureAnnotation(node, sourceFile, tsInstance) {
|
|
|
238
265
|
}
|
|
239
266
|
return false;
|
|
240
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
|
+
}
|
|
241
275
|
function getMemberName(m, tsInstance) {
|
|
242
276
|
if (!m || !m.name) {
|
|
243
277
|
return '';
|
|
@@ -259,7 +293,12 @@ function getMemberName(m, tsInstance) {
|
|
|
259
293
|
function isReadonlyPropertyAccess(a, typeChecker, tsInstance) {
|
|
260
294
|
const ts = tsInstance;
|
|
261
295
|
const type = typeChecker.getTypeAtLocation(a.expression);
|
|
262
|
-
const memberName =
|
|
296
|
+
const memberName = ts.isPropertyAccessExpression(a)
|
|
297
|
+
? a.name.getText()
|
|
298
|
+
: getNameFromElementAccessExpression(a, typeChecker);
|
|
299
|
+
if (memberName == null) {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
263
302
|
if (type.getFlags() & ts.TypeFlags.Object) {
|
|
264
303
|
const dummyTypeNode = typeChecker.typeToTypeNode(type, a, ts.NodeBuilderFlags.NoTruncation);
|
|
265
304
|
if (dummyTypeNode && ts.isTypeLiteralNode(dummyTypeNode)) {
|
|
@@ -309,7 +348,8 @@ function isReadonlyExpression(node, program, tsInstance) {
|
|
|
309
348
|
}
|
|
310
349
|
}
|
|
311
350
|
}
|
|
312
|
-
if (ts.isPropertyAccessExpression(node)
|
|
351
|
+
if (ts.isPropertyAccessExpression(node) ||
|
|
352
|
+
ts.isElementAccessExpression(node)) {
|
|
313
353
|
if (isEnumAccess(node, program, ts)) {
|
|
314
354
|
return true;
|
|
315
355
|
}
|
|
@@ -317,6 +357,42 @@ function isReadonlyExpression(node, program, tsInstance) {
|
|
|
317
357
|
}
|
|
318
358
|
return null;
|
|
319
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
|
+
}
|
|
320
396
|
////////////////////////////////////////////////////////////////////////////////
|
|
321
397
|
export function printSource(sourceFile) {
|
|
322
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",
|