Skip to content

Commit ddf0757

Browse files
build: enable typescript sources
1 parent 66f19dd commit ddf0757

File tree

10 files changed

+218
-66
lines changed

10 files changed

+218
-66
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ tools/*/*.i.tmp
115115
/tools/clang-format/node_modules
116116
/tools/eslint/node_modules
117117
/tools/lint-md/node_modules
118+
/tools/typescript/node_modules
118119

119120
# === Rules for test artifacts ===
120121
/*.tap

Makefile

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1468,6 +1468,29 @@ lint-js-ci: tools/eslint/node_modules/eslint/bin/eslint.js
14681468
jslint-ci: lint-js-ci
14691469
$(warning Please use lint-js-ci instead of jslint-ci)
14701470

1471+
# TypeScript type checking
1472+
tools/typescript/node_modules/typescript/bin/tsc: tools/typescript/package.json
1473+
-cd tools/typescript && $(call available-node,$(run-npm-ci))
1474+
1475+
.PHONY: typecheck-build
1476+
typecheck-build: ## Install TypeScript type checker dependencies
1477+
$(info Installing TypeScript for type checking...)
1478+
cd tools/typescript && $(call available-node,$(run-npm-ci))
1479+
1480+
.PHONY: typecheck
1481+
typecheck: ## Type-check TypeScript files in lib/
1482+
@if [ -f "tools/typescript/node_modules/typescript/bin/tsc" ]; then \
1483+
$(MAKE) run-typecheck ; \
1484+
else \
1485+
echo 'TypeScript type checking is not available'; \
1486+
echo "Run 'make typecheck-build'"; \
1487+
fi
1488+
1489+
.PHONY: run-typecheck
1490+
run-typecheck:
1491+
$(info Running TypeScript type checker...)
1492+
@$(call available-node,tools/typescript/node_modules/typescript/bin/tsc)
1493+
14711494
LINT_CPP_ADDON_DOC_FILES_GLOB = test/addons/??_*/*.cc test/addons/??_*/*.h
14721495
LINT_CPP_ADDON_DOC_FILES = $(wildcard $(LINT_CPP_ADDON_DOC_FILES_GLOB))
14731496
LINT_CPP_EXCLUDE ?=
@@ -1636,11 +1659,12 @@ lint: ## Run JS, C++, MD and doc linters.
16361659
$(MAKE) lint-addon-docs || EXIT_STATUS=$$? ; \
16371660
$(MAKE) lint-md || EXIT_STATUS=$$? ; \
16381661
$(MAKE) lint-yaml || EXIT_STATUS=$$? ; \
1662+
$(MAKE) typecheck || EXIT_STATUS=$$? ; \
16391663
exit $$EXIT_STATUS
16401664
CONFLICT_RE=^>>>>>>> [[:xdigit:]]+|^<<<<<<< [[:alpha:]]+
16411665

16421666
# Related CI job: node-test-linter
1643-
lint-ci: lint-js-ci lint-cpp lint-py lint-md lint-addon-docs lint-yaml-build lint-yaml
1667+
lint-ci: lint-js-ci lint-cpp lint-py lint-md lint-addon-docs lint-yaml-build lint-yaml typecheck
16441668
@if ! ( grep -IEqrs "$(CONFLICT_RE)" --exclude="error-message.js" --exclude="merge-conflict.json" benchmark deps doc lib src test tools ) \
16451669
&& ! ( $(FIND) . -maxdepth 1 -type f | xargs grep -IEqs "$(CONFLICT_RE)" ); then \
16461670
exit 0 ; \
@@ -1661,6 +1685,7 @@ lint-clean: ## Remove linting artifacts.
16611685
$(RM) .eslintcache
16621686
$(RM) -r tools/eslint/node_modules
16631687
$(RM) -r tools/lint-md/node_modules
1688+
$(RM) -r tools/typescript/node_modules
16641689
$(RM) tools/pip/site_packages
16651690

16661691
HAS_DOCKER ?= $(shell command -v docker > /dev/null 2>&1; [ $$? -eq 0 ] && echo 1 || echo 0)

deps/amaro/lib/wasm.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ interface JsxConfig {
6161

6262

6363

64-
type Mode = "strip-only" | "transform";
64+
export type Mode = "strip-only" | "transform";
6565

6666

6767

Lines changed: 27 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
'use strict';
22

3+
type Options = import('../../../deps/amaro/lib/wasm').Options;
4+
type TransformOutput = import('../../../deps/amaro/lib/wasm').TransformOutput;
5+
type Mode = 'strip-only' | 'transform';
6+
37
const {
48
ObjectPrototypeHasOwnProperty,
59
} = primordials;
@@ -9,11 +13,13 @@ const {
913
validateObject,
1014
validateString,
1115
} = require('internal/validators');
12-
const { assertTypeScript,
13-
emitExperimentalWarning,
14-
getLazy,
15-
isUnderNodeModules,
16-
kEmptyObject } = require('internal/util');
16+
const {
17+
assertTypeScript,
18+
emitExperimentalWarning,
19+
getLazy,
20+
isUnderNodeModules,
21+
kEmptyObject
22+
} = require('internal/util');
1723
const {
1824
ERR_INVALID_TYPESCRIPT_SYNTAX,
1925
ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING,
@@ -30,31 +36,26 @@ const {
3036

3137
/**
3238
* The TypeScript parsing mode, either 'strip-only' or 'transform'.
33-
* @type {function(): TypeScriptMode}
3439
*/
35-
const getTypeScriptParsingMode = getLazy(() =>
36-
(getOptionValue('--experimental-transform-types') ?
37-
(emitExperimentalWarning('Transform Types'), 'transform') : 'strip-only'),
40+
const getTypeScriptParsingMode: () => Mode = getLazy(() =>
41+
(getOptionValue('--experimental-transform-types') ?
42+
(emitExperimentalWarning('Transform Types'), 'transform') : 'strip-only'),
3843
);
3944

4045
/**
4146
* Load the TypeScript parser.
4247
* and returns an object with a `code` property.
43-
* @returns {Function} The TypeScript parser function.
4448
*/
45-
const loadTypeScriptParser = getLazy(() => {
49+
const loadTypeScriptParser: () => (source: string, options: Options) => TransformOutput = getLazy(() => {
4650
assertTypeScript();
47-
const amaro = require('internal/deps/amaro/dist/index');
51+
const amaro: { transformSync: (source: string, options: Options) => TransformOutput } = require('internal/deps/amaro/dist/index');
4852
return amaro.transformSync;
4953
});
5054

5155
/**
52-
*
53-
* @param {string} source the source code
54-
* @param {object} options the options to pass to the parser
55-
* @returns {TransformOutput} an object with a `code` property.
56+
* Parse TypeScript source code.
5657
*/
57-
function parseTypeScript(source, options) {
58+
function parseTypeScript(source: string, options: Options): TransformOutput {
5859
const parse = loadTypeScriptParser();
5960
try {
6061
return parse(source, options);
@@ -82,24 +83,18 @@ function parseTypeScript(source, options) {
8283
}
8384

8485
/**
85-
*
86-
* @param {Error} error the error to decorate: ERR_INVALID_TYPESCRIPT_SYNTAX, ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX
87-
* @param {object} amaroError the error object from amaro
88-
* @returns {Error} the decorated error
86+
* Decorate an error with a code snippet from the Amaro error.
8987
*/
90-
function decorateErrorWithSnippet(error, amaroError) {
88+
function decorateErrorWithSnippet(error: Error, amaroError: any): Error {
9189
const errorHints = `${amaroError.filename}:${amaroError.startLine}\n${amaroError.snippet}`;
9290
error.stack = `${errorHints}\n${error.stack}`;
9391
return error;
9492
}
9593

9694
/**
9795
* Performs type-stripping to TypeScript source code.
98-
* @param {string} code TypeScript code to parse.
99-
* @param {TransformOptions} options The configuration for type stripping.
100-
* @returns {string} The stripped TypeScript code.
10196
*/
102-
function stripTypeScriptTypes(code, options = kEmptyObject) {
97+
function stripTypeScriptTypes(code: string, options = kEmptyObject): string {
10398
emitExperimentalWarning('stripTypeScriptTypes');
10499
validateString(code, 'code');
105100
validateObject(options, 'options');
@@ -127,22 +122,11 @@ function stripTypeScriptTypes(code, options = kEmptyObject) {
127122
});
128123
}
129124

130-
/**
131-
* @typedef {'strip-only' | 'transform'} TypeScriptMode
132-
* @typedef {object} TypeScriptOptions
133-
* @property {TypeScriptMode} mode Mode.
134-
* @property {boolean} sourceMap Whether to generate source maps.
135-
* @property {string|undefined} filename Filename.
136-
*/
137-
138125
/**
139126
* Processes TypeScript code by stripping types or transforming.
140127
* Handles source maps if needed.
141-
* @param {string} code TypeScript code to process.
142-
* @param {TypeScriptOptions} options The configuration object.
143-
* @returns {string} The processed code.
144128
*/
145-
function processTypeScriptCode(code, options) {
129+
function processTypeScriptCode(code: string, options: Options): string {
146130
const { code: transformedCode, map } = parseTypeScript(code, options);
147131

148132
if (map) {
@@ -158,11 +142,8 @@ function processTypeScriptCode(code, options) {
158142

159143
/**
160144
* Get the type enum used for compile cache.
161-
* @param {TypeScriptMode} mode Mode of transpilation.
162-
* @param {boolean} sourceMap Whether source maps are enabled.
163-
* @returns {number}
164145
*/
165-
function getCachedCodeType(mode, sourceMap) {
146+
function getCachedCodeType(mode: Mode, sourceMap: boolean): number {
166147
if (mode === 'transform') {
167148
if (sourceMap) { return kTransformedTypeScriptWithSourceMaps; }
168149
return kTransformedTypeScript;
@@ -173,11 +154,8 @@ function getCachedCodeType(mode, sourceMap) {
173154
/**
174155
* Performs type-stripping to TypeScript source code internally.
175156
* It is used by internal loaders.
176-
* @param {string} source TypeScript code to parse.
177-
* @param {string} filename The filename of the source code.
178-
* @returns {TransformOutput} The stripped TypeScript code.
179157
*/
180-
function stripTypeScriptModuleTypes(source, filename) {
158+
function stripTypeScriptModuleTypes(source: string, filename: string): string {
181159
assert(typeof source === 'string');
182160
if (isUnderNodeModules(filename)) {
183161
throw new ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING(filename);
@@ -200,7 +178,7 @@ function stripTypeScriptModuleTypes(source, filename) {
200178
return cached.transpiled;
201179
}
202180

203-
const options = {
181+
const options: Options = {
204182
mode,
205183
sourceMap,
206184
filename,
@@ -219,12 +197,9 @@ function stripTypeScriptModuleTypes(source, filename) {
219197
}
220198

221199
/**
222-
*
223-
* @param {string} code The compiled code.
224-
* @param {string} sourceMap The source map.
225-
* @returns {string} The code with the source map attached.
200+
* Add a source map to compiled code.
226201
*/
227-
function addSourceMap(code, sourceMap) {
202+
function addSourceMap(code: string, sourceMap: string): string {
228203
// The base64 encoding should be https://datatracker.ietf.org/doc/html/rfc4648#section-4,
229204
// not base64url https://datatracker.ietf.org/doc/html/rfc4648#section-5. See data url
230205
// spec https://tools.ietf.org/html/rfc2397#section-2.

src/node_builtins.cc

Lines changed: 107 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -178,19 +178,99 @@ BuiltinLoader::BuiltinCategories BuiltinLoader::GetBuiltinCategories() const {
178178
}
179179

180180
#ifdef NODE_BUILTIN_MODULES_PATH
181-
static std::string OnDiskFileName(const char* id) {
182-
std::string filename = NODE_BUILTIN_MODULES_PATH;
183-
filename += "/";
181+
static std::string OnDiskFileName(const char* id,
182+
bool* is_typescript = nullptr) {
183+
std::string base_path = NODE_BUILTIN_MODULES_PATH;
184+
base_path += "/";
184185

185186
if (strncmp(id, "internal/deps", strlen("internal/deps")) == 0) {
186-
id += strlen("internal/");
187+
base_path += id + strlen("internal/");
187188
} else {
188-
filename += "lib/";
189+
base_path += "lib/";
190+
base_path += id;
189191
}
190-
filename += id;
191-
filename += ".js";
192192

193-
return filename;
193+
// Try .ts file first
194+
std::string ts_filename = base_path + ".ts";
195+
uv_fs_t req;
196+
int r = uv_fs_stat(nullptr, &req, ts_filename.c_str(), nullptr);
197+
uv_fs_req_cleanup(&req);
198+
199+
if (r == 0) {
200+
if (is_typescript != nullptr) *is_typescript = true;
201+
return ts_filename;
202+
}
203+
204+
// Fall back to .js file
205+
if (is_typescript != nullptr) *is_typescript = false;
206+
return base_path + ".js";
207+
}
208+
209+
// Strip TypeScript types by calling the JavaScript stripTypeScriptModuleTypes
210+
// function
211+
static MaybeLocal<String> StripTypeScriptTypes(Local<Context> context,
212+
const std::string& source,
213+
const std::string& filename) {
214+
Isolate* isolate = context->GetIsolate();
215+
EscapableHandleScope scope(isolate);
216+
217+
// Get the current environment
218+
Environment* env = Environment::GetCurrent(context);
219+
if (env == nullptr) {
220+
// Runtime not initialized yet, cannot strip types
221+
// Return the source as-is (this shouldn't happen in practice)
222+
return String::NewFromUtf8(
223+
isolate, source.c_str(), NewStringType::kNormal, source.length());
224+
}
225+
226+
// Require the typescript module
227+
Local<Value> typescript_module;
228+
if (!env->builtin_module_require()
229+
->Call(context,
230+
Undefined(isolate),
231+
1,
232+
&FIXED_ONE_BYTE_STRING(isolate, "internal/modules/typescript")
233+
.As<Value>())
234+
.ToLocal(&typescript_module) ||
235+
!typescript_module->IsObject()) {
236+
// Cannot load typescript module, return source as-is
237+
return String::NewFromUtf8(
238+
isolate, source.c_str(), NewStringType::kNormal, source.length());
239+
}
240+
241+
// Get the stripTypeScriptModuleTypes function
242+
Local<Object> typescript_obj = typescript_module.As<Object>();
243+
Local<Value> strip_fn;
244+
if (!typescript_obj
245+
->Get(context,
246+
FIXED_ONE_BYTE_STRING(isolate, "stripTypeScriptModuleTypes"))
247+
.ToLocal(&strip_fn) ||
248+
!strip_fn->IsFunction()) {
249+
// Function not found, return source as-is
250+
return String::NewFromUtf8(
251+
isolate, source.c_str(), NewStringType::kNormal, source.length());
252+
}
253+
254+
// Call stripTypeScriptModuleTypes(source, filename)
255+
Local<Value> args[2] = {
256+
String::NewFromUtf8(
257+
isolate, source.c_str(), NewStringType::kNormal, source.length())
258+
.ToLocalChecked(),
259+
String::NewFromUtf8(
260+
isolate, filename.c_str(), NewStringType::kNormal, filename.length())
261+
.ToLocalChecked()};
262+
263+
Local<Value> result;
264+
if (!strip_fn.As<Function>()
265+
->Call(context, Undefined(isolate), 2, args)
266+
.ToLocal(&result) ||
267+
!result->IsString()) {
268+
// Stripping failed, return original source
269+
return String::NewFromUtf8(
270+
isolate, source.c_str(), NewStringType::kNormal, source.length());
271+
}
272+
273+
return scope.Escape(result.As<String>());
194274
}
195275
#endif // NODE_BUILTIN_MODULES_PATH
196276

@@ -283,6 +363,25 @@ MaybeLocal<Function> BuiltinLoader::LookupAndCompileInternal(
283363
return {};
284364
}
285365

366+
#ifdef NODE_BUILTIN_MODULES_PATH
367+
// Check if this is a TypeScript file and strip types if needed
368+
bool is_typescript = false;
369+
std::string filename_for_check = OnDiskFileName(id, &is_typescript);
370+
371+
if (is_typescript) {
372+
// Convert source to std::string
373+
node::Utf8Value utf8_source(isolate, source);
374+
std::string source_str(*utf8_source, utf8_source.length());
375+
376+
// Strip TypeScript types
377+
if (!StripTypeScriptTypes(context, source_str, filename_for_check)
378+
.ToLocal(&source)) {
379+
// If stripping fails, use the original source
380+
// (this will likely cause a syntax error, but better than crashing)
381+
}
382+
}
383+
#endif // NODE_BUILTIN_MODULES_PATH
384+
286385
std::string filename_s = std::string("node:") + id;
287386
Local<String> filename = OneByteString(isolate, filename_s);
288387
ScriptOrigin origin(filename, 0, 0, true);

0 commit comments

Comments
 (0)