forked from nodejs/node
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtypescript.js
More file actions
238 lines (217 loc) Β· 7.93 KB
/
typescript.js
File metadata and controls
238 lines (217 loc) Β· 7.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
'use strict';
const {
ObjectPrototypeHasOwnProperty,
} = primordials;
const {
validateBoolean,
validateOneOf,
validateObject,
validateString,
} = require('internal/validators');
const { assertTypeScript,
emitExperimentalWarning,
getLazy,
isUnderNodeModules,
kEmptyObject } = require('internal/util');
const {
ERR_INVALID_TYPESCRIPT_SYNTAX,
ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING,
ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX,
} = require('internal/errors').codes;
const { getOptionValue } = require('internal/options');
const assert = require('internal/assert');
const { Buffer } = require('buffer');
const {
getCompileCacheEntry,
saveCompileCacheEntry,
cachedCodeTypes: { kStrippedTypeScript, kTransformedTypeScript, kTransformedTypeScriptWithSourceMaps },
} = internalBinding('modules');
/**
* The TypeScript parsing mode, either 'strip-only' or 'transform'.
* @type {function(): TypeScriptMode}
*/
const getTypeScriptParsingMode = getLazy(() =>
(getOptionValue('--experimental-transform-types') ?
(emitExperimentalWarning('Transform Types'), 'transform') : 'strip-only'),
);
/**
* Load the TypeScript parser.
* and returns an object with a `code` property.
* @returns {Function} The TypeScript parser function.
*/
const loadTypeScriptParser = getLazy(() => {
assertTypeScript();
const amaro = require('internal/deps/amaro/dist/index');
return amaro.transformSync;
});
/**
*
* @param {string} source the source code
* @param {object} options the options to pass to the parser
* @returns {TransformOutput} an object with a `code` property.
*/
function parseTypeScript(source, options) {
const parse = loadTypeScriptParser();
try {
return parse(source, options);
} catch (error) {
/**
* Amaro v0.3.0 (from SWC v1.10.7) throws an object with `message` and `code` properties.
* It allows us to distinguish between invalid syntax and unsupported syntax.
*/
switch (error?.code) {
case 'UnsupportedSyntax': {
const unsupportedSyntaxError = new ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX(error.message);
throw decorateErrorWithSnippet(unsupportedSyntaxError, error); /* node-do-not-add-exception-line */
}
case 'InvalidSyntax': {
const invalidSyntaxError = new ERR_INVALID_TYPESCRIPT_SYNTAX(error.message);
throw decorateErrorWithSnippet(invalidSyntaxError, error); /* node-do-not-add-exception-line */
}
default:
// SWC may throw strings when something goes wrong.
if (typeof error === 'string') { assert.fail(error); }
assert(error != null && ObjectPrototypeHasOwnProperty(error, 'message'));
assert.fail(error.message);
}
}
}
/**
*
* @param {Error} error the error to decorate: ERR_INVALID_TYPESCRIPT_SYNTAX, ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX
* @param {object} amaroError the error object from amaro
* @returns {Error} the decorated error
*/
function decorateErrorWithSnippet(error, amaroError) {
const errorHints = `${amaroError.filename}:${amaroError.startLine}\n${amaroError.snippet}`;
error.stack = `${errorHints}\n${error.stack}`;
return error;
}
/**
* Performs type-stripping to TypeScript source code.
* @param {string} code TypeScript code to parse.
* @param {TransformOptions} options The configuration for type stripping.
* @returns {string} The stripped TypeScript code.
*/
function stripTypeScriptTypes(code, options = kEmptyObject) {
emitExperimentalWarning('stripTypeScriptTypes');
validateString(code, 'code');
validateObject(options, 'options');
const {
sourceMap = false,
sourceUrl = '',
} = options;
let { mode = 'strip' } = options;
validateOneOf(mode, 'options.mode', ['strip', 'transform']);
validateBoolean(sourceMap, 'options.sourceMap');
validateString(sourceUrl, 'options.sourceUrl');
if (mode === 'strip') {
validateOneOf(sourceMap, 'options.sourceMap', [false, undefined]);
// Rename mode from 'strip' to 'strip-only'.
// The reason is to match `process.features.typescript` which returns `strip`,
// but the parser expects `strip-only`.
mode = 'strip-only';
}
return processTypeScriptCode(code, {
mode,
sourceMap,
filename: sourceUrl,
});
}
/**
* @typedef {'strip-only' | 'transform'} TypeScriptMode
* @typedef {object} TypeScriptOptions
* @property {TypeScriptMode} mode Mode.
* @property {boolean} sourceMap Whether to generate source maps.
* @property {string|undefined} filename Filename.
*/
/**
* Processes TypeScript code by stripping types or transforming.
* Handles source maps if needed.
* @param {string} code TypeScript code to process.
* @param {TypeScriptOptions} options The configuration object.
* @returns {string} The processed code.
*/
function processTypeScriptCode(code, options) {
const { code: transformedCode, map } = parseTypeScript(code, options);
if (map) {
return addSourceMap(transformedCode, map);
}
if (options.filename) {
return `${transformedCode}\n\n//# sourceURL=${options.filename}`;
}
return transformedCode;
}
/**
* Get the type enum used for compile cache.
* @param {TypeScriptMode} mode Mode of transpilation.
* @param {boolean} sourceMap Whether source maps are enabled.
* @returns {number}
*/
function getCachedCodeType(mode, sourceMap) {
if (mode === 'transform') {
if (sourceMap) { return kTransformedTypeScriptWithSourceMaps; }
return kTransformedTypeScript;
}
return kStrippedTypeScript;
}
/**
* Performs type-stripping to TypeScript source code internally.
* It is used by internal loaders.
* @param {string} source TypeScript code to parse.
* @param {string} filename The filename of the source code.
* @returns {TransformOutput} The stripped TypeScript code.
*/
function stripTypeScriptModuleTypes(source, filename) {
assert(typeof source === 'string');
if (isUnderNodeModules(filename)) {
throw new ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING(filename);
}
const sourceMap = getOptionValue('--enable-source-maps');
const mode = getTypeScriptParsingMode();
// Instead of caching the compile cache status, just go into C++ to fetch it,
// as checking process.env equally involves calling into C++ anyway, and
// the compile cache can be enabled dynamically.
const type = getCachedCodeType(mode, sourceMap);
// Get a compile cache entry into the native compile cache store,
// keyed by the filename. If the cache can already be loaded on disk,
// cached.transpiled contains the cached string. Otherwise we should do
// the transpilation and save it in the native store later using
// saveCompileCacheEntry().
const cached = (filename ? getCompileCacheEntry(source, filename, type) : undefined);
if (cached?.transpiled) { // TODO(joyeecheung): return Buffer here.
return cached.transpiled;
}
const options = {
mode,
sourceMap,
filename,
};
const transpiled = processTypeScriptCode(source, options);
if (cached) {
// cached.external contains a pointer to the native cache entry.
// The cached object would be unreachable once it's out of scope,
// but the pointer inside cached.external would stay around for reuse until
// environment shutdown or when the cache is manually flushed
// to disk. Unwrap it in JS before passing into C++ since it's faster.
saveCompileCacheEntry(cached.external, transpiled);
}
return transpiled;
}
/**
*
* @param {string} code The compiled code.
* @param {string} sourceMap The source map.
* @returns {string} The code with the source map attached.
*/
function addSourceMap(code, sourceMap) {
// The base64 encoding should be https://datatracker.ietf.org/doc/html/rfc4648#section-4,
// not base64url https://datatracker.ietf.org/doc/html/rfc4648#section-5. See data url
// spec https://tools.ietf.org/html/rfc2397#section-2.
const base64SourceMap = Buffer.from(sourceMap).toString('base64');
return `${code}\n\n//# sourceMappingURL=data:application/json;base64,${base64SourceMap}`;
}
module.exports = {
stripTypeScriptModuleTypes,
stripTypeScriptTypes,
};