diff --git a/lib/eslint.config_partial.mjs b/lib/eslint.config_partial.mjs index 520df40aecbc90..2dc041ce893ae3 100644 --- a/lib/eslint.config_partial.mjs +++ b/lib/eslint.config_partial.mjs @@ -13,6 +13,15 @@ const noRestrictedSyntax = [ selector: "CallExpression[callee.object.name='assert']:not([callee.property.name='ok']):not([callee.property.name='fail']):not([callee.property.name='ifError'])", message: 'Only use simple assertions', }, + // Forbids usages of `btoa` like: + // ``` + // const { btoa } = internalBinding('buffer'); + // btoa('...'); + // ``` + { + selector: "CallExpression[callee.property.name='btoa'], CallExpression[callee.name='btoa']", + message: "`btoa` supports only latin-1 charset, use Buffer.from(str).toString('base64') instead", + }, { selector: 'NewExpression[callee.name=/Error$/]:not([callee.name=/^(AssertionError|NghttpError|AbortError|NodeAggregateError)$/])', message: "Use an error exported by 'internal/errors' instead.", diff --git a/lib/internal/modules/typescript.js b/lib/internal/modules/typescript.js index d1b58e86c72ee7..2f8f61266b5d03 100644 --- a/lib/internal/modules/typescript.js +++ b/lib/internal/modules/typescript.js @@ -17,6 +17,7 @@ const { } = require('internal/errors').codes; const { getOptionValue } = require('internal/options'); const assert = require('internal/assert'); +const { Buffer } = require('buffer'); /** * The TypeScript parsing mode, either 'strip-only' or 'transform'. @@ -134,9 +135,10 @@ function stripTypeScriptModuleTypes(source, filename) { * @returns {string} The code with the source map attached. */ function addSourceMap(code, sourceMap) { - // TODO(@marco-ippolito) When Buffer.transcode supports utf8 to - // base64 transformation, we should change this line. - const base64SourceMap = internalBinding('buffer').btoa(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}`; } diff --git a/test/parallel/test-module-strip-types.js b/test/parallel/test-module-strip-types.js index 6e729a55936804..0f90039b563033 100644 --- a/test/parallel/test-module-strip-types.js +++ b/test/parallel/test-module-strip-types.js @@ -12,6 +12,12 @@ common.expectWarning( 'stripTypeScriptTypes is an experimental feature and might change at any time', ); +const sourceToBeTransformed = ` + namespace MathUtil { + export const add = (a: number, b: number) => a + b; + }`; +const sourceToBeTransformedMapping = 'UACY;aACK,MAAM,CAAC,GAAW,IAAc,IAAI;AACnD,GAFU,aAAA'; + test('stripTypeScriptTypes', () => { const source = 'const x: number = 1;'; const result = stripTypeScriptTypes(source); @@ -48,45 +54,52 @@ test('stripTypeScriptTypes sourceUrl throws when mode is strip', () => { }); test('stripTypeScriptTypes source map when mode is transform', () => { - const source = ` - namespace MathUtil { - export const add = (a: number, b: number) => a + b; - }`; - const result = stripTypeScriptTypes(source, { mode: 'transform', sourceMap: true }); + const result = stripTypeScriptTypes(sourceToBeTransformed, { mode: 'transform', sourceMap: true }); const script = new vm.Script(result); const sourceMap = { version: 3, - sources: [ - '', - ], - sourcesContent: [ - '\n namespace MathUtil {\n export const add = (a: number, b: number) => a + b;\n }', - ], + sources: [''], names: [], - mappings: ';UACY;aACK,MAAM,CAAC,GAAW,IAAc,IAAI;AACnD,GAFU,aAAA' + mappings: sourceToBeTransformedMapping, }; - assert(script.sourceMapURL, `sourceMappingURL=data:application/json;base64,${JSON.stringify(sourceMap)}`); + const inlinedSourceMap = Buffer.from(JSON.stringify(sourceMap)).toString('base64'); + assert.strictEqual(script.sourceMapURL, `data:application/json;base64,${inlinedSourceMap}`); }); test('stripTypeScriptTypes source map when mode is transform and sourceUrl', () => { - const source = ` - namespace MathUtil { - export const add = (a: number, b: number) => a + b; - }`; - const result = stripTypeScriptTypes(source, { mode: 'transform', sourceMap: true, sourceUrl: 'test.ts' }); + const result = stripTypeScriptTypes(sourceToBeTransformed, { + mode: 'transform', + sourceMap: true, + sourceUrl: 'test.ts' + }); + const script = new vm.Script(result); + const sourceMap = + { + version: 3, + sources: ['test.ts'], + names: [], + mappings: sourceToBeTransformedMapping, + }; + const inlinedSourceMap = Buffer.from(JSON.stringify(sourceMap)).toString('base64'); + assert.strictEqual(script.sourceMapURL, `data:application/json;base64,${inlinedSourceMap}`); +}); + +test('stripTypeScriptTypes source map when mode is transform and sourceUrl with non-latin-1 chars', () => { + const sourceUrl = 'dir%20with $unusual"chars?\'åß∂ƒ©∆¬…`.cts'; + const result = stripTypeScriptTypes(sourceToBeTransformed, { + mode: 'transform', + sourceMap: true, + sourceUrl, + }); const script = new vm.Script(result); const sourceMap = { version: 3, - sources: [ - 'test.ts', - ], - sourcesContent: [ - '\n namespace MathUtil {\n export const add = (a: number, b: number) => a + b;\n }', - ], + sources: [sourceUrl], names: [], - mappings: ';UACY;aACK,MAAM,CAAC,GAAW,IAAc,IAAI;AACnD,GAFU,aAAA' + mappings: sourceToBeTransformedMapping, }; - assert(script.sourceMapURL, `sourceMappingURL=data:application/json;base64,${JSON.stringify(sourceMap)}`); + const inlinedSourceMap = Buffer.from(JSON.stringify(sourceMap)).toString('base64'); + assert.strictEqual(script.sourceMapURL, `data:application/json;base64,${inlinedSourceMap}`); });