From 977c2b6e9cc9daa74212e8ee159d553628361047 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 27 Jul 2020 23:05:36 -0700 Subject: [PATCH 1/7] Barebones basic wasm import support --- src/compiler/commandLineParser.ts | 7 + src/compiler/diagnosticMessages.json | 8 + src/compiler/emitter.ts | 8 +- src/compiler/moduleNameResolver.ts | 15 +- src/compiler/moduleSpecifiers.ts | 1 + src/compiler/parser.ts | 7 + src/compiler/program.ts | 7 +- src/compiler/sys.ts | 23 +- src/compiler/tsconfig.json | 3 + src/compiler/types.ts | 7 +- src/compiler/utilities.ts | 11 +- src/compiler/wasm/parser.ts | 435 ++++++++++++++++++ src/compiler/wasm/transform.ts | 115 +++++ src/harness/fakesHosts.ts | 9 +- src/services/stringCompletions.ts | 1 + src/services/types.ts | 1 + .../baselines/reference/wasmImportsSimple.js | 15 + .../reference/wasmImportsSimple.symbols | 21 + .../reference/wasmImportsSimple.types | 28 ++ .../wasm/imports/wasmImportsSimple.ts | 9 + tests/lib/test.wasm | Bin 0 -> 60 bytes 21 files changed, 705 insertions(+), 26 deletions(-) create mode 100644 src/compiler/wasm/parser.ts create mode 100644 src/compiler/wasm/transform.ts create mode 100644 tests/baselines/reference/wasmImportsSimple.js create mode 100644 tests/baselines/reference/wasmImportsSimple.symbols create mode 100644 tests/baselines/reference/wasmImportsSimple.types create mode 100644 tests/cases/conformance/wasm/imports/wasmImportsSimple.ts create mode 100644 tests/lib/test.wasm diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 26b8641e2d753..4a049ba329c2c 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -757,6 +757,13 @@ namespace ts { category: Diagnostics.Experimental_Options, description: Diagnostics.Enables_experimental_support_for_emitting_type_metadata_for_decorators }, + { + name: "experimentalWasmModules", + type: "boolean", + affectsSemanticDiagnostics: true, + category: Diagnostics.Experimental_Options, + description: Diagnostics.Enables_experimental_loading_of_type_information_from_experimental_wasm_modules + }, // Advanced { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index d0d66f0f959b8..78cf4429e0699 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4473,6 +4473,10 @@ "category": "Message", "code": 6235 }, + "Enables experimental loading of type information from experimental wasm modules.": { + "category": "Message", + "code": 6236 + }, "Projects to reference": { "category": "Message", @@ -4872,6 +4876,10 @@ "category": "Error", "code": 7055 }, + "Module '{0}' was resolved to '{1}', but '--experimentalWasmModules' is not used.": { + "category": "Error", + "code": 7056 + }, "You cannot rename this element.": { "category": "Error", diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index f976c38ea6af8..5e0ab567be505 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1226,10 +1226,14 @@ namespace ts { return getPipelinePhase(currentPhase + 1, emitHint, node); } + function getTextPos() { + return writer.getTextPos(); + } + function pipelineEmitWithNotification(hint: EmitHint, node: Node) { Debug.assert(lastNode === node); const pipelinePhase = getNextPipelinePhase(PipelinePhase.Notification, hint, node); - onEmitNode(hint, node, pipelinePhase); + onEmitNode(hint, node, pipelinePhase, getTextPos); Debug.assert(lastNode === node); } @@ -2903,7 +2907,7 @@ namespace ts { emitSignatureHead(node); if (onEmitNode) { - onEmitNode(EmitHint.Unspecified, body, emitBlockCallback); + onEmitNode(EmitHint.Unspecified, body, emitBlockCallback, getTextPos); } else { emitBlockFunctionBody(body); diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index ad331c60be7f2..055c6dc82b742 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -67,6 +67,7 @@ namespace ts { TypeScript, /** '.ts', '.tsx', or '.d.ts' */ JavaScript, /** '.js' or '.jsx' */ Json, /** '.json' */ + Wasm, /** '.wasm' */ TSConfig, /** '.json' with `tsconfig` used instead of `index` */ DtsOnly /** Only '.d.ts' */ } @@ -912,7 +913,6 @@ namespace ts { const jsOnlyExtensions = [Extensions.JavaScript]; const tsExtensions = [Extensions.TypeScript, Extensions.JavaScript]; - const tsPlusJsonExtensions = [...tsExtensions, Extensions.Json]; const tsconfigExtensions = [Extensions.TSConfig]; function tryResolveJSModuleWorker(moduleName: string, initialDir: string, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { return nodeModuleNameResolverWorker(moduleName, initialDir, { moduleResolution: ModuleResolutionKind.NodeJs, allowJs: true }, host, /*cache*/ undefined, jsOnlyExtensions, /*redirectedReferences*/ undefined); @@ -921,7 +921,7 @@ namespace ts { export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations; /* @internal */ export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations; // eslint-disable-line @typescript-eslint/unified-signatures export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations { - return nodeModuleNameResolverWorker(moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, lookupConfig ? tsconfigExtensions : (compilerOptions.resolveJsonModule ? tsPlusJsonExtensions : tsExtensions), redirectedReference); + return nodeModuleNameResolverWorker(moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, lookupConfig ? tsconfigExtensions : [...tsExtensions, ...(compilerOptions.resolveJsonModule ? [Extensions.Json] : []), ...(compilerOptions.experimentalWasmModules ? [Extensions.Wasm] : [])], redirectedReference); } function nodeModuleNameResolverWorker(moduleName: string, containingDirectory: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache: ModuleResolutionCache | undefined, extensions: Extensions[], redirectedReference: ResolvedProjectReference | undefined): ResolvedModuleWithFailedLookupLocations { @@ -1057,9 +1057,9 @@ namespace ts { * in cases when we know upfront that all load attempts will fail (because containing folder does not exists) however we still need to record all failed lookup locations. */ function loadModuleFromFile(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { - if (extensions === Extensions.Json || extensions === Extensions.TSConfig) { - const extensionLess = tryRemoveExtension(candidate, Extension.Json); - return (extensionLess === undefined && extensions === Extensions.Json) ? undefined : tryAddingExtensions(extensionLess || candidate, extensions, onlyRecordFailures, state); + if (extensions === Extensions.Json || extensions === Extensions.TSConfig || extensions === Extensions.Wasm) { + const extensionLess = tryRemoveExtension(candidate, Extension.Json) || tryRemoveExtension(candidate, Extension.Wasm); + return (extensionLess === undefined && (extensions === Extensions.Json || extensions === Extensions.Wasm)) ? undefined : tryAddingExtensions(extensionLess || candidate, extensions, onlyRecordFailures, state); } // First, try adding an extension. An import of "foo" could be matched by a file "foo.ts", or "foo.js" by "foo.js.ts" @@ -1100,6 +1100,8 @@ namespace ts { case Extensions.TSConfig: case Extensions.Json: return tryExtension(Extension.Json); + case Extensions.Wasm: + return tryExtension(Extension.Wasm); } function tryExtension(ext: Extension): PathAndExtension | undefined { @@ -1168,6 +1170,7 @@ namespace ts { switch (extensions) { case Extensions.JavaScript: case Extensions.Json: + case Extensions.Wasm: packageFile = readPackageJsonMainField(jsonContent, candidate, state); break; case Extensions.TypeScript: @@ -1243,6 +1246,8 @@ namespace ts { return extension === Extension.Ts || extension === Extension.Tsx || extension === Extension.Dts; case Extensions.DtsOnly: return extension === Extension.Dts; + case Extensions.Wasm: + return extension === Extension.Wasm; } } diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index 54be63be022fa..deb7ce6dd98c9 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -515,6 +515,7 @@ namespace ts.moduleSpecifiers { case Extension.Js: case Extension.Jsx: case Extension.Json: + case Extension.Wasm: return ext; case Extension.TsBuildInfo: return Debug.fail(`Extension ${Extension.TsBuildInfo} is unsupported:: FileName:: ${fileName}`); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 1729e3a810c4a..ad920fe341e4c 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -823,6 +823,13 @@ namespace ts { result.pragmas = emptyMap as ReadonlyPragmaMap; return result; } + else if (scriptKind === ScriptKind.Wasm) { + const result = wasm.declarationsFor(wasm.parse(fileName, (sourceText as any).buffer)); + if (setParentNodes) { + fixupParentReferences(result); + } + return result; + } initializeState(fileName, sourceText, languageVersion, syntaxCursor, scriptKind); diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 87f4637258cb0..e1ef874c3ad70 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -2843,7 +2843,7 @@ namespace ts { } const isFromNodeModulesSearch = resolution.isExternalLibraryImport; - const isJsFile = !resolutionExtensionIsTSOrJson(resolution.extension); + const isJsFile = !resolutionExtensionIsTSOrJson(resolution.extension) && resolution.extension !== Extension.Wasm; const isJsFileFromNodeModules = isFromNodeModulesSearch && isJsFile; const resolvedFileName = resolution.resolvedFileName; @@ -3785,6 +3785,8 @@ namespace ts { return needAllowJs(); case Extension.Json: return needResolveJsonModule(); + case Extension.Wasm: + return needExperimentalWasmModules(); } function needJsx() { @@ -3796,6 +3798,9 @@ namespace ts { function needResolveJsonModule() { return options.resolveJsonModule ? undefined : Diagnostics.Module_0_was_resolved_to_1_but_resolveJsonModule_is_not_used; } + function needExperimentalWasmModules() { + return options.experimentalWasmModules ? undefined : Diagnostics.Module_0_was_resolved_to_1_but_experimentalWasmModules_is_not_used; + } } function getModuleNames({ imports, moduleAugmentations }: SourceFile): string[] { diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 1662a7ef31096..a3f72a59756be 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -1551,14 +1551,7 @@ namespace ts { } } - function readFileWorker(fileName: string, _encoding?: string): string | undefined { - let buffer: Buffer; - try { - buffer = _fs.readFileSync(fileName); - } - catch (e) { - return undefined; - } + function decodeBuffer(buffer: Buffer) { let len = buffer.length; if (len >= 2 && buffer[0] === 0xFE && buffer[1] === 0xFF) { // Big endian UTF-16 byte order mark detected. Since big endian is not supported by node.js, @@ -1583,6 +1576,20 @@ namespace ts { return buffer.toString("utf8"); } + function readFileWorker(fileName: string, _encoding?: string): string | undefined { + let buffer: Buffer; + try { + buffer = _fs.readFileSync(fileName); + } + catch (e) { + return undefined; + } + const result = decodeBuffer(buffer); + const wrapped = new String(result); + (wrapped as any).buffer = buffer; + return wrapped as string; + } + function readFile(fileName: string, _encoding?: string): string | undefined { perfLogger.logStartReadFile(fileName); const file = readFileWorker(fileName, _encoding); diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index 8213e019b340e..caf3eba81bd3a 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -38,6 +38,9 @@ "commandLineParser.ts", "moduleNameResolver.ts", + "wasm/parser.ts", + "wasm/transform.ts", + "binder.ts", "symbolWalker.ts", "checker.ts", diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 24e43f87ca579..f04288de33492 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5675,6 +5675,7 @@ namespace ts { emitBOM?: boolean; emitDecoratorMetadata?: boolean; experimentalDecorators?: boolean; + experimentalWasmModules?: boolean; forceConsistentCasingInFileNames?: boolean; /*@internal*/generateCpuProfile?: string; /*@internal*/help?: boolean; @@ -5830,6 +5831,7 @@ namespace ts { TSX = 4, External = 5, JSON = 6, + Wasm = 7, /** * Used on extensions that doesn't define the ScriptKind but the content defines it. * Deferred extensions are going to be included in all project contexts. @@ -6177,7 +6179,8 @@ namespace ts { Js = ".js", Jsx = ".jsx", Json = ".json", - TsBuildInfo = ".tsbuildinfo" + TsBuildInfo = ".tsbuildinfo", + Wasm = ".wasm", } export interface ResolvedModuleWithFailedLookupLocations { @@ -7601,7 +7604,7 @@ namespace ts { * }); * ``` */ - onEmitNode?(hint: EmitHint, node: Node | undefined, emitCallback: (hint: EmitHint, node: Node | undefined) => void): void; + onEmitNode?(hint: EmitHint, node: Node | undefined, emitCallback: (hint: EmitHint, node: Node | undefined) => void, getTextPos: () => number): void; /** * A hook used to check if an emit notification is required for a node. diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 41d513a252303..142f16fe4f541 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -6329,6 +6329,8 @@ namespace ts { return ScriptKind.TSX; case Extension.Json: return ScriptKind.JSON; + case Extension.Wasm: + return ScriptKind.Wasm; default: return ScriptKind.Unknown; } @@ -6344,7 +6346,6 @@ namespace ts { export const supportedJSExtensions: readonly Extension[] = [Extension.Js, Extension.Jsx]; export const supportedJSAndJsonExtensions: readonly Extension[] = [Extension.Js, Extension.Jsx, Extension.Json]; const allSupportedExtensions: readonly Extension[] = [...supportedTSExtensions, ...supportedJSExtensions]; - const allSupportedExtensionsWithJson: readonly Extension[] = [...supportedTSExtensions, ...supportedJSExtensions, Extension.Json]; export function getSupportedExtensions(options?: CompilerOptions): readonly Extension[]; export function getSupportedExtensions(options?: CompilerOptions, extraFileExtensions?: readonly FileExtensionInfo[]): readonly string[]; @@ -6364,10 +6365,8 @@ namespace ts { } export function getSuppoertedExtensionsWithJsonIfResolveJsonModule(options: CompilerOptions | undefined, supportedExtensions: readonly string[]): readonly string[] { - if (!options || !options.resolveJsonModule) { return supportedExtensions; } - if (supportedExtensions === allSupportedExtensions) { return allSupportedExtensionsWithJson; } - if (supportedExtensions === supportedTSExtensions) { return supportedTSExtensionsWithJson; } - return [...supportedExtensions, Extension.Json]; + if (!options || (!options.resolveJsonModule && !options.experimentalWasmModules)) { return supportedExtensions; } + return [...supportedExtensions, ...(options.resolveJsonModule ? [Extension.Json] : []), ...(options.experimentalWasmModules ? [Extension.Wasm] : [])]; } function isJSLike(scriptKind: ScriptKind | undefined): boolean { @@ -6446,7 +6445,7 @@ namespace ts { } } - const extensionsToRemove = [Extension.Dts, Extension.Ts, Extension.Js, Extension.Tsx, Extension.Jsx, Extension.Json]; + const extensionsToRemove = [Extension.Dts, Extension.Ts, Extension.Js, Extension.Tsx, Extension.Jsx, Extension.Json, Extension.Wasm]; export function removeFileExtension(path: string): string { for (const ext of extensionsToRemove) { const extensionless = tryRemoveExtension(path, ext); diff --git a/src/compiler/wasm/parser.ts b/src/compiler/wasm/parser.ts new file mode 100644 index 0000000000000..fd4824c632da2 --- /dev/null +++ b/src/compiler/wasm/parser.ts @@ -0,0 +1,435 @@ +namespace ts.wasm { + export interface WasmSourceFile { + readonly fileName: string; + readonly magic: [number, number, number, number]; + readonly version: number; + readonly sections: WasmSection[]; + } + + export enum SectionKind { + Custom, + Type, + Import, + Function, + Table, + Memory, + Global, + Export, + Start, + Element, + Code, + Data, + } + + export function parse(fileName: string, buf: Uint8Array): WasmSourceFile { + let sections: WasmSection[]; + let magic: [number, number, number, number]; + Debug.assert(buf.length >= 8); // Minimum size of a wasm file: "magic" 4 bytes + version TODO: Issue diagnostic on failure + return { + fileName, + get magic() { + return magic ||= [buf[0], buf[1], buf[2], buf[3]]; + }, + get version() { + return (buf as Buffer).readUInt32LE(4); // cast to node builtin read methods to save time putting this together TODO: probably don't need this, since we don't really check the `version` anywhere yet + }, + get sections() { + if (sections) { + return sections; + } + + const result = []; + const cursor: Cursor = { index: 8 }; + while (true) { + if (cursor.index >= buf.length) { + break; + } + const id = buf[cursor.index]; + cursor.index++; + const size = parseUnsignedLEB128u32(cursor); + result.push(createWasmSection(id as SectionKind, cursor.index, size)); + cursor.index += size; + } + return sections = result; + } + }; + + interface Cursor { + index: number; + } + + function parseExpected(cursor: Cursor, byte: number) { + Debug.assert(buf[cursor.index] === byte); // TODO: Issue diagnostic on malformed wasm, rather than crash + cursor.index++; + } + + function parseUnsignedLEB128u32(cursor: Cursor): number { + let result = 0; + let offset = 0; + while (true) { + const b = buf[cursor.index]; + cursor.index++; + result |= (b & 0b0111_1111) << offset; + if ((b & 0b1000_0000) === 0) { + break; + } + offset += 7; + } + return result; + } + + function parseVector(cursor: Cursor, parseElement: (cursor: Cursor) => T): T[] { + const size = parseUnsignedLEB128u32(cursor); + const elements: T[] = []; + for (let i = 0; i < size; i++) { + elements.push(parseElement(cursor)); + } + return elements; + } + + function parseValueType(cursor: Cursor): ValueType { + const byte = buf[cursor.index]; + switch (byte) { + case ValueType.i32: + case ValueType.i64: + case ValueType.f32: + case ValueType.f64: + cursor.index++; + return byte as ValueType; + default: + Debug.fail(`Unknown value type: ${byte}`); // TODO: Issue diagnostic + } + } + + function parseResultTypes(cursor: Cursor): ValueType[] { + return parseVector(cursor, parseValueType); + } + + function parseFunctionType(cursor: Cursor): [parameters: ValueType[], returns: ValueType[]] { + parseExpected(cursor, 0x60); // Function type section start byte + const paramTypes = parseResultTypes(cursor); + const returnTypes = parseResultTypes(cursor); + return [paramTypes, returnTypes]; + } + + function parseUtf8Character(cursor: Cursor): string { + const b1 = buf[cursor.index++]; + if ((b1 & 0b1000_0000) === 0) { + return utf16EncodeAsString(b1); + } + const b2 = buf[cursor.index++]; + if ((b1 & 0b0010_0000) === 0) { + return utf16EncodeAsString(((b1 & 0b0001_1111) << 6) | (b2 & 0b0011_1111)); + } + const b3 = buf[cursor.index++]; + if ((b1 & 0b0001_0000) === 0) { + return utf16EncodeAsString( + ((b1 & 0b0000_1111) << 12) | + ((b2 & 0b0011_1111) << 6) | + (b3 & 0b0011_1111) + ); + } + const b4 = buf[cursor.index++]; + return utf16EncodeAsString( + ((b1 & 0b0000_0111) << 18) | + ((b2 & 0b0011_1111) << 12) | + ((b3 & 0b0011_1111) << 6) | + (b4 & 0b0011_1111) + ); + } + + function parseUtf8String(cursor: Cursor, byteLength: number): string { + const start = cursor.index; + Debug.assert((start + byteLength) < buf.byteLength); // TODO: Issue malformed file diagnostic if untrue + let result = ""; + while (cursor.index < (start + byteLength)) { + result += parseUtf8Character(cursor); + } + Debug.assert(cursor.index === start + byteLength); // TODO: Issue malformed file diagnostic if untrue + return result; + } + + function parseName(cursor: Cursor): string { + const byteLength = parseUnsignedLEB128u32(cursor); + return parseUtf8String(cursor, byteLength); + } + + function parseExportDescription(cursor: Cursor): ExportDescription { + const byte = buf[cursor.index]; + switch (byte) { + case ExportKind.Func: + case ExportKind.Table: + case ExportKind.Mem: + case ExportKind.Global: + cursor.index++; + const index = parseUnsignedLEB128u32(cursor); + return { kind: byte as ExportKind, index }; + default: + Debug.fail(`Unknown export kind: ${byte}`); // TODO: Issue diagnostic + } + } + + function parseWasmExport(cursor: Cursor): WasmExport { + const name = parseName(cursor); + const exportdesc = parseExportDescription(cursor); + return { name, exportdesc }; + } + + function createWasmSection(kind: SectionKind, start: number, size: number): WasmSection { + switch (kind) { + case SectionKind.Custom: return createCustomSection(start, size); + case SectionKind.Type: return createTypeSection(start, size); + case SectionKind.Import: return createImportSection(start, size); + case SectionKind.Function: return createFunctionSection(start, size); + case SectionKind.Table: return createTableSection(start, size); + case SectionKind.Memory: return createMemorySection(start, size); + case SectionKind.Global: return createGlobalSection(start, size); + case SectionKind.Export: return createExportSection(start, size); + case SectionKind.Start: return createStartSection(start, size); + case SectionKind.Element: return createElementSection(start, size); + case SectionKind.Code: return createCodeSection(start, size); + case SectionKind.Data: return createDataSection(start, size); + default: Debug.assertNever(kind); + } + } + + function createCustomSection(start: number, size: number): CustomSection { + // TODO: How should we reinterpret a custom section into a specific type of custom section + // such as the `name` section? + let name: string; + let bytes: Uint8Array; + return { + id: SectionKind.Custom, + start, + size, + get name() { + if (name) { + return name; + } + const nameCursor: Cursor = { index: start }; + name = parseName(nameCursor); + const nameSize = (nameCursor.index - start); + const bytesSize = size - nameSize; + // TODO: This makes a view on node (which is what we want), but would clone the underlying memory in a browser + // It might be better to explicitly make a new ArrayBufferView over the same backing buffer for consistent behavior + // across platforms + bytes = buf.slice(nameCursor.index, nameCursor.index + bytesSize); + return name; + }, + get bytes() { + this.name; + return bytes; + } + }; + } + + function createTypeSection(start: number, size: number): TypeSection { + let funcs: TypeSection["funcs"]; + return { + id: SectionKind.Type, + start, + size, + get funcs() { + if (funcs) { + return funcs; + } + const cursor: Cursor = { index: start }; + return funcs = parseVector(cursor, parseFunctionType); + } + }; + } + + function createImportSection(start: number, size: number): ImportSection { + return { + id: SectionKind.Import, + start, + size + }; + } + + function createFunctionSection(start: number, size: number): FunctionSection { + let indices: number[]; + return { + id: SectionKind.Function, + start, + size, + get indices() { + if (indices) { + return indices; + } + const cursor: Cursor = { index: start }; + return indices = parseVector(cursor, parseUnsignedLEB128u32); + } + }; + } + + function createTableSection(start: number, size: number): TableSection { + return { + id: SectionKind.Table, + start, + size + }; + } + + function createMemorySection(start: number, size: number): MemorySection { + return { + id: SectionKind.Memory, + start, + size + }; + } + + function createGlobalSection(start: number, size: number): GlobalSection { + return { + id: SectionKind.Global, + start, + size + }; + } + + function createExportSection(start: number, size: number): ExportSection { + let exports: WasmExport[]; + return { + id: SectionKind.Export, + start, + size, + get exports() { + if (exports) { + return exports; + } + const cursor: Cursor = { index: start }; + return exports = parseVector(cursor, parseWasmExport); + } + }; + } + + function createStartSection(start: number, size: number): StartSection { + return { + id: SectionKind.Start, + start, + size + }; + } + + function createElementSection(start: number, size: number): ElementSection { + return { + id: SectionKind.Element, + start, + size + }; + } + + function createCodeSection(start: number, size: number): CodeSection { + return { + id: SectionKind.Code, + start, + size + }; + } + + function createDataSection(start: number, size: number): DataSection { + return { + id: SectionKind.Data, + start, + size + }; + } + } + + export type WasmSection = + | CustomSection + | TypeSection + | ImportSection + | FunctionSection + | TableSection + | MemorySection + | GlobalSection + | ExportSection + | StartSection + | ElementSection + | CodeSection + | DataSection; + + export interface SectionBase { + readonly id: SectionKind; + readonly start: number; + readonly size: number; + } + + export interface CustomSection extends SectionBase { + readonly id: SectionKind.Custom; + readonly name: string; + readonly bytes: Uint8Array; + } + + export interface TypeSection extends SectionBase { + readonly id: SectionKind.Type; + readonly funcs: [parameters: ValueType[], results: ValueType[]][] + } + + export interface ImportSection extends SectionBase { + readonly id: SectionKind.Import; + } + + export interface FunctionSection extends SectionBase { + readonly id: SectionKind.Function; + readonly indices: number[]; + } + + export interface TableSection extends SectionBase { + readonly id: SectionKind.Table; + } + + export interface MemorySection extends SectionBase { + readonly id: SectionKind.Memory; + } + + export interface GlobalSection extends SectionBase { + readonly id: SectionKind.Global; + } + + export interface ExportSection extends SectionBase { + readonly id: SectionKind.Export; + readonly exports: WasmExport[]; + } + + export interface StartSection extends SectionBase { + readonly id: SectionKind.Start; + } + + export interface ElementSection extends SectionBase { + readonly id: SectionKind.Element; + } + + export interface CodeSection extends SectionBase { + readonly id: SectionKind.Code; + } + + export interface DataSection extends SectionBase { + readonly id: SectionKind.Data; + } + + export enum ValueType { + i32 = 0x7F, + i64 = 0x7E, + f32 = 0x7D, + f64 = 0x7C, + } + + export interface WasmExport { + readonly name: string; + readonly exportdesc: ExportDescription; + } + + export enum ExportKind { + Func = 0x00, + Table = 0x01, + Mem = 0x02, + Global = 0x03, + } + + export interface ExportDescription { + readonly kind: ExportKind; + readonly index: number; + } +} \ No newline at end of file diff --git a/src/compiler/wasm/transform.ts b/src/compiler/wasm/transform.ts new file mode 100644 index 0000000000000..3e4047540eabb --- /dev/null +++ b/src/compiler/wasm/transform.ts @@ -0,0 +1,115 @@ +namespace ts.wasm { + + function getSection(file: WasmSourceFile, kind: SectionKind.Custom): CustomSection | undefined; + function getSection(file: WasmSourceFile, kind: SectionKind.Code): CodeSection | undefined; + function getSection(file: WasmSourceFile, kind: SectionKind.Data): DataSection | undefined; + function getSection(file: WasmSourceFile, kind: SectionKind.Element): ElementSection | undefined; + function getSection(file: WasmSourceFile, kind: SectionKind.Export): ExportSection | undefined; + function getSection(file: WasmSourceFile, kind: SectionKind.Function): FunctionSection | undefined; + function getSection(file: WasmSourceFile, kind: SectionKind.Global): GlobalSection | undefined; + function getSection(file: WasmSourceFile, kind: SectionKind.Import): ImportSection | undefined; + function getSection(file: WasmSourceFile, kind: SectionKind.Memory): MemorySection | undefined; + function getSection(file: WasmSourceFile, kind: SectionKind.Start): StartSection | undefined; + function getSection(file: WasmSourceFile, kind: SectionKind.Table): TableSection | undefined; + function getSection(file: WasmSourceFile, kind: SectionKind.Type): TypeSection | undefined; + function getSection(file: WasmSourceFile, kind: SectionKind): WasmSection | undefined { + return find(file.sections, s => s.id === kind); + } + + export function declarationsFor(file: WasmSourceFile): SourceFile { + const factory = createNodeFactory(NodeFactoryFlags.NoOriginalNode | NodeFactoryFlags.NoNodeConverters, parseBaseNodeFactory); + const exports = find(file.sections, s => s.id === SectionKind.Export) as ExportSection | undefined; // There should only be one + const statements = !exports ? [finishNode(factory.createExportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + finishNode(factory.createNamedExports([]))))] : map(exports.exports, createExportedStatement); + const eofToken = factory.createToken(SyntaxKind.EndOfFileToken); + const result = factory.createSourceFile(statements, eofToken, NodeFlags.Ambient); + result.referencedFiles = emptyArray; + result.typeReferenceDirectives = emptyArray; + result.libReferenceDirectives = emptyArray; + result.amdDependencies = emptyArray; + result.hasNoDefaultLib = false; + result.pragmas = emptyMap as ReadonlyPragmaMap; + result.parseDiagnostics = []; + result.isDeclarationFile = true; + result.fileName = `${file.fileName}.d.ts`; + result.bindDiagnostics = []; + result.bindSuggestionDiagnostics = undefined; + result.languageVersion = ScriptTarget.Latest; + result.languageVariant = getLanguageVariant(ScriptKind.TS); + result.scriptKind = ScriptKind.TS; + result.externalModuleIndicator = result.statements[0]; + result.text = ""; + // Immediately printing the synthetic file declaration text allows us to generate concrete positions + // for all the nodes we've synthesized, and essentially un-synthesize them (making them appear, by all + // rights, to be veritable parse tree nodes) + result.text = createPrinter({}, { + onEmitNode(hint, node, cb, getTextPos) { + const start = getTextPos(); + cb(hint, node); + if (node) { + (node as Mutable).pos = start; + (node as Mutable).end = getTextPos(); + } + }, + + }).printFile(result); + (eofToken as Mutable).pos = result.text.length; + (eofToken as Mutable).end = result.text.length; + // The above sets all node positions, but node _arrays_ still have `-1` for their pos and end. + // We fix those up to use their constituent start and end positions here. + fixupNodeArrays(result); + return result; + + function fixupNodeArrays(node: Node) { + forEachChildRecursively(node, noop, (arr) => { + if (arr.length) { + (arr as Mutable).pos = arr[0].pos; + (arr as Mutable).end = arr[arr.length - 1].end; + } + }); + } + + function finishNode(node: T) { + (node as Mutable).flags |= NodeFlags.Ambient; + return node; + } + + function createExportedStatement(e: WasmExport): Statement { + switch(e.exportdesc.kind) { + case ExportKind.Func: { + const funcTypeSection = getSection(file, SectionKind.Type); + Debug.assert(funcTypeSection); + const functionSection = getSection(file, SectionKind.Function); + Debug.assert(functionSection); + const typeIndex = functionSection.indices[e.exportdesc.index]; + const sig = funcTypeSection.funcs[typeIndex]; + return finishNode(factory.createFunctionDeclaration( + /*decorators*/ undefined, + [finishNode(factory.createModifier(SyntaxKind.ExportKeyword))], + /*asteriskToken*/ undefined, + e.name, + /*typeParameters*/ undefined, + map(sig[0], (_, i) => finishNode(factory.createParameterDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + `arg${i}`, + /*questionToken*/ undefined, + finishNode(factory.createKeywordTypeNode(SyntaxKind.NumberKeyword)) + ))), + finishNode(factory.createKeywordTypeNode(SyntaxKind.NumberKeyword)), + /*body*/ undefined + )); + } + case ExportKind.Global: + case ExportKind.Mem: + case ExportKind.Table: + default: + return Debug.fail("Unhandled export kind"); + } + } + } +} \ No newline at end of file diff --git a/src/harness/fakesHosts.ts b/src/harness/fakesHosts.ts index 8d7db1d333cb3..b4edcf09d22f0 100644 --- a/src/harness/fakesHosts.ts +++ b/src/harness/fakesHosts.ts @@ -43,8 +43,13 @@ namespace fakes { public readFile(path: string) { try { - const content = this.vfs.readFileSync(path, "utf8"); - return content === undefined ? undefined : Utils.removeByteOrderMark(content); + let content = this.vfs.readFileSync(path, "utf8"); + if (content === undefined) { + return undefined; + } + const wrapped = new String(Utils.removeByteOrderMark(content)); + (wrapped as any).buffer = this.vfs.readFileSync(path); + return wrapped as string; } catch { return undefined; diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts index c3473b6251eae..6bb9708b7f7b6 100644 --- a/src/services/stringCompletions.ts +++ b/src/services/stringCompletions.ts @@ -88,6 +88,7 @@ namespace ts.Completions.StringCompletions { case Extension.Jsx: return ScriptElementKindModifier.jsxModifier; case Extension.Ts: return ScriptElementKindModifier.tsModifier; case Extension.Tsx: return ScriptElementKindModifier.tsxModifier; + case Extension.Wasm: return ScriptElementKindModifier.wasmModifier; case Extension.TsBuildInfo: return Debug.fail(`Extension ${Extension.TsBuildInfo} is unsupported.`); case undefined: return ScriptElementKindModifier.none; default: diff --git a/src/services/types.ts b/src/services/types.ts index 0ebd80372cbea..d4a8448bc9401 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -1340,6 +1340,7 @@ namespace ts { jsModifier = ".js", jsxModifier = ".jsx", jsonModifier = ".json", + wasmModifier = ".wasm", } export const enum ClassificationTypeNames { diff --git a/tests/baselines/reference/wasmImportsSimple.js b/tests/baselines/reference/wasmImportsSimple.js new file mode 100644 index 0000000000000..d1a05c0bb0605 --- /dev/null +++ b/tests/baselines/reference/wasmImportsSimple.js @@ -0,0 +1,15 @@ +//// [wasmImportsSimple.ts] +import * as mod from "/.lib/test.wasm"; + +let running = 0; +for (let i = 0; i < 10; i++) { + running += mod.addTwo(i, i); +} + + +//// [wasmImportsSimple.js] +import * as mod from "test.wasm"; +let running = 0; +for (let i = 0; i < 10; i++) { + running += mod.addTwo(i, i); +} diff --git a/tests/baselines/reference/wasmImportsSimple.symbols b/tests/baselines/reference/wasmImportsSimple.symbols new file mode 100644 index 0000000000000..1a1706118b381 --- /dev/null +++ b/tests/baselines/reference/wasmImportsSimple.symbols @@ -0,0 +1,21 @@ +=== tests/cases/conformance/wasm/imports/wasmImportsSimple.ts === +import * as mod from "test.wasm"; +>mod : Symbol(mod, Decl(wasmImportsSimple.ts, 0, 6)) + +let running = 0; +>running : Symbol(running, Decl(wasmImportsSimple.ts, 2, 3)) + +for (let i = 0; i < 10; i++) { +>i : Symbol(i, Decl(wasmImportsSimple.ts, 3, 8)) +>i : Symbol(i, Decl(wasmImportsSimple.ts, 3, 8)) +>i : Symbol(i, Decl(wasmImportsSimple.ts, 3, 8)) + + running += mod.addTwo(i, i); +>running : Symbol(running, Decl(wasmImportsSimple.ts, 2, 3)) +>mod.addTwo : Symbol(mod.addTwo, Decl(test.wasm, 0, 0)) +>mod : Symbol(mod, Decl(wasmImportsSimple.ts, 0, 6)) +>addTwo : Symbol(mod.addTwo, Decl(test.wasm, 0, 0)) +>i : Symbol(i, Decl(wasmImportsSimple.ts, 3, 8)) +>i : Symbol(i, Decl(wasmImportsSimple.ts, 3, 8)) +} + diff --git a/tests/baselines/reference/wasmImportsSimple.types b/tests/baselines/reference/wasmImportsSimple.types new file mode 100644 index 0000000000000..9265ece4f7ca6 --- /dev/null +++ b/tests/baselines/reference/wasmImportsSimple.types @@ -0,0 +1,28 @@ +=== tests/cases/conformance/wasm/imports/wasmImportsSimple.ts === +import * as mod from "test.wasm"; +>mod : typeof mod + +let running = 0; +>running : number +>0 : 0 + +for (let i = 0; i < 10; i++) { +>i : number +>0 : 0 +>i < 10 : boolean +>i : number +>10 : 10 +>i++ : number +>i : number + + running += mod.addTwo(i, i); +>running += mod.addTwo(i, i) : number +>running : number +>mod.addTwo(i, i) : number +>mod.addTwo : (arg0: number, arg1: number) => number +>mod : typeof mod +>addTwo : (arg0: number, arg1: number) => number +>i : number +>i : number +} + diff --git a/tests/cases/conformance/wasm/imports/wasmImportsSimple.ts b/tests/cases/conformance/wasm/imports/wasmImportsSimple.ts new file mode 100644 index 0000000000000..682c28ec442ba --- /dev/null +++ b/tests/cases/conformance/wasm/imports/wasmImportsSimple.ts @@ -0,0 +1,9 @@ +// @experimentalWasmModules: true +// @target: es2020 +// @moduleResolution: node +import * as mod from "/.lib/test.wasm"; + +let running = 0; +for (let i = 0; i < 10; i++) { + running += mod.addTwo(i, i); +} diff --git a/tests/lib/test.wasm b/tests/lib/test.wasm new file mode 100644 index 0000000000000000000000000000000000000000..6359537606b7cda3df45dc20ffcdfb4a2cd640b7 GIT binary patch literal 60 zcmZQbEY4+QU|?WmXG~zKuV<`hW@2Pu=VD|_Oi2kT&u3uZ;$&oJP+(AC%;IL?W64X* OO=V(dWMBg7U;qFZCkQ?O literal 0 HcmV?d00001 From 71e4fb7ffab8236cb03271daf81e1342c506081d Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 28 Jul 2020 00:51:32 -0700 Subject: [PATCH 2/7] Unbork tests by un-hacking buffer loading --- src/compiler/parser.ts | 9 +- src/compiler/program.ts | 7 +- src/compiler/sys.ts | 18 ++- src/compiler/types.ts | 6 +- src/compiler/watch.ts | 6 +- src/compiler/watchPublic.ts | 1 + src/compiler/watchUtilities.ts | 2 + src/harness/fakesHosts.ts | 27 ++++- src/harness/harnessIO.ts | 5 +- src/harness/harnessLanguageService.ts | 14 +++ src/harness/virtualFileSystemWithWatch.ts | 8 ++ src/jsTyping/jsTyping.ts | 1 + src/server/project.ts | 5 + src/services/services.ts | 5 + src/services/shims.ts | 5 + src/services/transpile.ts | 1 + src/services/types.ts | 1 + src/testRunner/rwcRunner.ts | 3 +- src/testRunner/unittests/config/showConfig.ts | 1 + src/testRunner/unittests/customTransforms.ts | 1 + src/testRunner/unittests/moduleResolution.ts | 14 ++- src/testRunner/unittests/programApi.ts | 1 + .../unittests/reuseProgramStructure.ts | 1 + src/testRunner/unittests/tsserver/session.ts | 1 + .../APISample_WatchWithOwnWatchHost.js | 2 + .../reference/api/tsserverlibrary.d.ts | 113 +++++++++++++++++- tests/baselines/reference/api/typescript.d.ts | 112 ++++++++++++++++- .../experimentalWasmModules/tsconfig.json | 5 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../declarationDir-is-specified.js | 1 + ...-outDir-and-declarationDir-is-specified.js | 1 + .../when-outDir-is-specified.js | 1 + .../with-outFile.js | 1 + ...e-is-specified-with-declaration-enabled.js | 1 + .../without-outDir-or-outFile-is-specified.js | 1 + .../APISample_WatchWithOwnWatchHost.ts | 1 + 44 files changed, 360 insertions(+), 31 deletions(-) create mode 100644 tests/baselines/reference/showConfig/Shows tsconfig for single option/experimentalWasmModules/tsconfig.json diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index ad920fe341e4c..e5e225ded4ac6 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -603,7 +603,7 @@ namespace ts { } } - export function createSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes = false, scriptKind?: ScriptKind): SourceFile { + export function createSourceFile(fileName: string, sourceText: string | Uint8Array, languageVersion: ScriptTarget, setParentNodes = false, scriptKind?: ScriptKind): SourceFile { performance.mark("beforeParse"); let result: SourceFile; @@ -810,9 +810,10 @@ namespace ts { // attached to the EOF token. let parseErrorBeforeNextFinishedNode = false; - export function parseSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, syntaxCursor: IncrementalParser.SyntaxCursor | undefined, setParentNodes = false, scriptKind?: ScriptKind): SourceFile { + export function parseSourceFile(fileName: string, sourceText: string | Uint8Array, languageVersion: ScriptTarget, syntaxCursor: IncrementalParser.SyntaxCursor | undefined, setParentNodes = false, scriptKind?: ScriptKind): SourceFile { scriptKind = ensureScriptKind(fileName, scriptKind); if (scriptKind === ScriptKind.JSON) { + Debug.assert(isString(sourceText)); const result = parseJsonText(fileName, sourceText, languageVersion, syntaxCursor, setParentNodes); convertToObjectWorker(result, result.parseDiagnostics, /*returnValue*/ false, /*knownRootOptions*/ undefined, /*jsonConversionNotifier*/ undefined); result.referencedFiles = emptyArray; @@ -824,13 +825,15 @@ namespace ts { return result; } else if (scriptKind === ScriptKind.Wasm) { - const result = wasm.declarationsFor(wasm.parse(fileName, (sourceText as any).buffer)); + Debug.assert(!isString(sourceText)); + const result = wasm.declarationsFor(wasm.parse(fileName, sourceText)); if (setParentNodes) { fixupParentReferences(result); } return result; } + Debug.assert(isString(sourceText)); initializeState(fileName, sourceText, languageVersion, syntaxCursor, scriptKind); const result = parseSourceFileWorker(languageVersion, setParentNodes, scriptKind); diff --git a/src/compiler/program.ts b/src/compiler/program.ts index e1ef874c3ad70..c97287c37b75c 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -74,10 +74,10 @@ namespace ts { const existingDirectories = new Map(); const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames); function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile | undefined { - let text: string | undefined; + let text: string | Uint8Array | undefined; try { performance.mark("beforeIORead"); - text = compilerHost.readFile(fileName); + text = endsWith(fileName, ".wasm") ? compilerHost.readFileBuffer(fileName) : compilerHost.readFile(fileName); performance.mark("afterIORead"); performance.measure("I/O Read", "beforeIORead", "afterIORead"); } @@ -179,6 +179,7 @@ namespace ts { getNewLine: () => newLine, fileExists: fileName => system.fileExists(fileName), readFile: fileName => system.readFile(fileName), + readFileBuffer: fileName => system.readFileBuffer(fileName), trace: (s: string) => system.write(s + newLine), directoryExists: directoryName => system.directoryExists(directoryName), getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "", @@ -3709,6 +3710,7 @@ namespace ts { getCurrentDirectory(): string; fileExists(fileName: string): boolean; readFile(fileName: string): string | undefined; + readFileBuffer(path: string): Uint8Array | undefined; readDirectory?(rootDir: string, extensions: readonly string[], excludes: readonly string[] | undefined, includes: readonly string[], depth?: number): string[]; trace?(s: string): void; onUnRecoverableConfigFileDiagnostic?: DiagnosticReporter; @@ -3723,6 +3725,7 @@ namespace ts { return directoryStructureHost.readDirectory(root, extensions, excludes, includes, depth); }, readFile: f => directoryStructureHost.readFile(f), + readFileBuffer: f => directoryStructureHost.readFileBuffer(f), useCaseSensitiveFileNames: host.useCaseSensitiveFileNames(), getCurrentDirectory: () => host.getCurrentDirectory(), onUnRecoverableConfigFileDiagnostic: host.onUnRecoverableConfigFileDiagnostic || returnUndefined, diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index a3f72a59756be..4331942a2ea31 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -1050,6 +1050,7 @@ namespace ts { write(s: string): void; writeOutputIsTTY?(): boolean; readFile(path: string, encoding?: string): string | undefined; + readFileBuffer(path: string): Uint8Array | undefined; getFileSize?(path: string): number; writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; @@ -1190,6 +1191,7 @@ namespace ts { return process.stdout.isTTY; }, readFile, + readFileBuffer, writeFile, watchFile, watchDirectory, @@ -1576,7 +1578,9 @@ namespace ts { return buffer.toString("utf8"); } - function readFileWorker(fileName: string, _encoding?: string): string | undefined { + function readFileWorker(fileName: string, _encoding: undefined, bufferOutput: true): Buffer | undefined; + function readFileWorker(fileName: string, _encoding?: string): string | undefined; + function readFileWorker(fileName: string, _encoding?: string, bufferOutput?: true): string | Buffer | undefined { let buffer: Buffer; try { buffer = _fs.readFileSync(fileName); @@ -1584,10 +1588,7 @@ namespace ts { catch (e) { return undefined; } - const result = decodeBuffer(buffer); - const wrapped = new String(result); - (wrapped as any).buffer = buffer; - return wrapped as string; + return bufferOutput ? buffer : decodeBuffer(buffer); } function readFile(fileName: string, _encoding?: string): string | undefined { @@ -1597,6 +1598,13 @@ namespace ts { return file; } + function readFileBuffer(fileName: string): Uint8Array | undefined { + perfLogger.logStartReadFile(fileName); + const file = readFileWorker(fileName, /*encoding*/ undefined, /*buffer*/ true); + perfLogger.logStopReadFile(); + return file; + } + function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void { perfLogger.logEvent("WriteFile: " + fileName); // If a BOM is required, emit one diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f04288de33492..3fc7e1cf80ba3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3624,6 +3624,7 @@ namespace ts { fileExists(path: string): boolean; readFile(path: string): string | undefined; + readFileBuffer(path: string): Uint8Array | undefined; trace?(s: string): void; } @@ -5831,12 +5832,12 @@ namespace ts { TSX = 4, External = 5, JSON = 6, - Wasm = 7, /** * Used on extensions that doesn't define the ScriptKind but the content defines it. * Deferred extensions are going to be included in all project contexts. */ - Deferred = 7 + Deferred = 7, + Wasm = 8, } export const enum ScriptTarget { @@ -6111,6 +6112,7 @@ namespace ts { // readFile function is used to read arbitrary text files on disk, i.e. when resolution procedure needs the content of 'package.json' // to determine location of bundled typings for node module readFile(fileName: string): string | undefined; + readFileBuffer(fileName: string): Uint8Array | undefined; trace?(s: string): void; directoryExists?(directoryName: string): boolean; /** diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 724d57f48b29d..35ae291576886 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -283,10 +283,10 @@ namespace ts { const hostGetNewLine = memoize(() => host.getNewLine()); return { getSourceFile: (fileName, languageVersion, onError) => { - let text: string | undefined; + let text: string | Uint8Array | undefined; try { performance.mark("beforeIORead"); - text = host.readFile(fileName, getCompilerOptions().charset); + text = endsWith(fileName, ".wasm") ? host.readFileBuffer(fileName) : host.readFile(fileName, getCompilerOptions().charset); performance.mark("afterIORead"); performance.measure("I/O Read", "beforeIORead", "afterIORead"); } @@ -308,6 +308,7 @@ namespace ts { getNewLine: () => getNewLineCharacter(getCompilerOptions(), hostGetNewLine), fileExists: f => host.fileExists(f), readFile: f => host.readFile(f), + readFileBuffer: f => host.readFileBuffer(f), trace: maybeBind(host, host.trace), directoryExists: maybeBind(directoryStructureHost, directoryStructureHost.directoryExists), getDirectories: maybeBind(directoryStructureHost, directoryStructureHost.getDirectories), @@ -368,6 +369,7 @@ namespace ts { getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), fileExists: path => system.fileExists(path), readFile: (path, encoding) => system.readFile(path, encoding), + readFileBuffer: path => system.readFileBuffer?.(path), directoryExists: path => system.directoryExists(path), getDirectories: path => system.getDirectories(path), readDirectory: (path, extensions, exclude, include, depth) => system.readDirectory(path, extensions, exclude, include, depth), diff --git a/src/compiler/watchPublic.ts b/src/compiler/watchPublic.ts index e5f7cb60eddf1..c5322e1297044 100644 --- a/src/compiler/watchPublic.ts +++ b/src/compiler/watchPublic.ts @@ -84,6 +84,7 @@ namespace ts { * if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well */ readFile(path: string, encoding?: string): string | undefined; + readFileBuffer(path: string): Uint8Array | undefined; /** If provided, used for module resolution as well as to handle directory structure */ directoryExists?(path: string): boolean; diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts index 6d4f02c1f380f..c27e02624f687 100644 --- a/src/compiler/watchUtilities.ts +++ b/src/compiler/watchUtilities.ts @@ -6,6 +6,7 @@ namespace ts { export interface DirectoryStructureHost { fileExists(path: string): boolean; readFile(path: string, encoding?: string): string | undefined; + readFileBuffer(path: string): Uint8Array | undefined; // TODO: GH#18217 Optional methods are frequently used as non-optional directoryExists?(path: string): boolean; @@ -50,6 +51,7 @@ namespace ts { useCaseSensitiveFileNames, fileExists, readFile: (path, encoding) => host.readFile(path, encoding), + readFileBuffer: path => host.readFileBuffer(path), directoryExists: host.directoryExists && directoryExists, getDirectories, readDirectory, diff --git a/src/harness/fakesHosts.ts b/src/harness/fakesHosts.ts index b4edcf09d22f0..1bc47c7871ec9 100644 --- a/src/harness/fakesHosts.ts +++ b/src/harness/fakesHosts.ts @@ -47,9 +47,20 @@ namespace fakes { if (content === undefined) { return undefined; } - const wrapped = new String(Utils.removeByteOrderMark(content)); - (wrapped as any).buffer = this.vfs.readFileSync(path); - return wrapped as string; + return Utils.removeByteOrderMark(content); + } + catch { + return undefined; + } + } + + public readFileBuffer(path: string) { + try { + let content = this.vfs.readFileSync(path); + if (content === undefined) { + return undefined; + } + return content; } catch { return undefined; @@ -211,6 +222,10 @@ namespace fakes { return this.sys.readFile(path); } + public readFileBuffer(path: string): Uint8Array | undefined { + return this.sys.readFileBuffer(path); + } + public readDirectory(path: string, extensions: string[], excludes: string[], includes: string[], depth: number): string[] { return this.sys.readDirectory(path, extensions, excludes, includes, depth); } @@ -298,6 +313,10 @@ namespace fakes { return this.sys.readFile(path); } + public readFileBuffer(path: string): Uint8Array | undefined { + return this.sys.readFileBuffer(path); + } + public writeFile(fileName: string, content: string, writeByteOrderMark: boolean) { if (writeByteOrderMark) content = Utils.addUTF8ByteOrderMark(content); this.sys.writeFile(fileName, content); @@ -333,7 +352,7 @@ namespace fakes { const existing = this._sourceFiles.get(canonicalFileName); if (existing) return existing; - const content = this.readFile(canonicalFileName); + const content = ts.endsWith(fileName, ".wasm") ? this.readFileBuffer(canonicalFileName) : this.readFile(canonicalFileName); if (content === undefined) return undefined; // A virtual file system may shadow another existing virtual file system. This diff --git a/src/harness/harnessIO.ts b/src/harness/harnessIO.ts index c7f04cf277d92..e111461d55e51 100644 --- a/src/harness/harnessIO.ts +++ b/src/harness/harnessIO.ts @@ -6,6 +6,7 @@ namespace Harness { resolvePath(path: string): string | undefined; getFileSize(path: string): number; readFile(path: string): string | undefined; + readFileBuffer(path: string): Uint8Array | undefined; writeFile(path: string, contents: string): void; directoryName(path: string): string | undefined; getDirectories(path: string): string[]; @@ -136,6 +137,7 @@ namespace Harness { resolvePath: (path: string) => ts.sys.resolvePath(path), getFileSize: (path: string) => ts.sys.getFileSize!(path), readFile: path => ts.sys.readFile(path), + readFileBuffer: path => ts.sys.readFileBuffer(path), writeFile: (path, content) => ts.sys.writeFile(path, content), directoryName, getDirectories: path => ts.sys.getDirectories(path), @@ -1256,7 +1258,8 @@ namespace Harness { useCaseSensitiveFileNames: false, readDirectory: () => [], fileExists: () => true, - readFile: (name) => ts.forEach(testUnitData, data => data.name.toLowerCase() === name.toLowerCase() ? data.content : undefined) + readFile: (name) => ts.forEach(testUnitData, data => data.name.toLowerCase() === name.toLowerCase() ? data.content : undefined), + readFileBuffer: name => ts.forEach(testUnitData, data => data.name.toLowerCase() === name.toLowerCase() ? ts.sys.bufferFrom?.(data.content) : undefined) }; // check if project has tsconfig.json in the list of files diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 69d7238cbec15..7d4a7e872345d 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -283,6 +283,10 @@ namespace Harness.LanguageService { return this.sys.readFile(path); } + readFileBuffer(path: string): Uint8Array | undefined { + return this.sys.readFileBuffer(path); + } + realpath(path: string): string { return this.sys.realpath(path); } @@ -325,6 +329,9 @@ namespace Harness.LanguageService { readFile: fileName => { const scriptInfo = this.getScriptInfo(fileName); return scriptInfo && scriptInfo.content; + }, + readFileBuffer: fileName => { + return this.readFileBuffer(fileName); } }; this.getModuleResolutionsForFile = (fileName) => { @@ -388,6 +395,9 @@ namespace Harness.LanguageService { const snapshot = this.nativeHost.getScriptSnapshot(fileName); return snapshot && ts.getSnapshotText(snapshot); } + readFileBuffer(fileName: string) { + return this.nativeHost.readFileBuffer(fileName); + } log(s: string): void { this.nativeHost.log(s); } trace(s: string): void { this.nativeHost.trace(s); } error(s: string): void { this.nativeHost.error(s); } @@ -738,6 +748,10 @@ namespace Harness.LanguageService { return snapshot && ts.getSnapshotText(snapshot); } + readFileBuffer(fileName: string): Uint8Array | undefined { + return this.host.readFileBuffer(fileName); + } + realpath(path: string) { return this.host.realpath(path); } diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index c0e66430e9a06..5970f18d59061 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -55,6 +55,7 @@ interface Array { length: number; [n: number]: T; }` export interface File { path: string; content: string; + buffer?: Uint8Array; fileSize?: number; } @@ -86,6 +87,7 @@ interface Array { length: number; [n: number]: T; }` interface FsFile extends FSEntryBase { content: string; + buffer?: Uint8Array; fileSize?: number; } @@ -797,6 +799,7 @@ interface Array { length: number; [n: number]: T; }` private toFsFile(file: File): FsFile { const fsFile = this.toFsEntry(file.path) as FsFile; fsFile.content = file.content; + fsFile.buffer = file.buffer; fsFile.fileSize = file.fileSize; return fsFile; } @@ -875,6 +878,11 @@ interface Array { length: number; [n: number]: T; }` return fsEntry ? fsEntry.content : undefined; } + readFileBuffer(s: string): Uint8Array | undefined { + const fsEntry = this.getRealFile(this.toFullPath(s)); + return fsEntry ? fsEntry.buffer : undefined; + } + getFileSize(s: string) { const path = this.toFullPath(s); const entry = this.fs.get(path)!; diff --git a/src/jsTyping/jsTyping.ts b/src/jsTyping/jsTyping.ts index ca765bd776fd3..3ed1dbe5078f1 100644 --- a/src/jsTyping/jsTyping.ts +++ b/src/jsTyping/jsTyping.ts @@ -5,6 +5,7 @@ namespace ts.JsTyping { directoryExists(path: string): boolean; fileExists(fileName: string): boolean; readFile(path: string, encoding?: string): string | undefined; + readFileBuffer(path: string): Uint8Array | undefined; readDirectory(rootDir: string, extensions: readonly string[], excludes: readonly string[] | undefined, includes: readonly string[] | undefined, depth?: number): string[]; } diff --git a/src/server/project.ts b/src/server/project.ts index c8adceaaed51d..1b262d2292ba1 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -431,6 +431,10 @@ namespace ts.server { return this.projectService.host.readFile(fileName); } + readFileBuffer(path: string): Uint8Array | undefined { + return this.projectService.host.readFileBuffer(path); + } + writeFile(fileName: string, content: string): void { return this.projectService.host.writeFile(fileName, content); } @@ -1649,6 +1653,7 @@ namespace ts.server { realpath: this.program.realpath || this.projectService.host.realpath?.bind(this.projectService.host), getCurrentDirectory: this.getCurrentDirectory.bind(this), readFile: this.projectService.host.readFile.bind(this.projectService.host), + readFileBuffer: this.projectService.host.readFileBuffer.bind(this.projectService.host), getDirectories: this.projectService.host.getDirectories.bind(this.projectService.host), trace: this.projectService.host.trace?.bind(this.projectService.host), }; diff --git a/src/services/services.ts b/src/services/services.ts index a2cf8f381c200..9a3dc81677b92 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1296,6 +1296,7 @@ namespace ts { getCurrentDirectory: () => currentDirectory, fileExists, readFile, + readFileBuffer, getSymlinkCache: maybeBind(host, host.getSymlinkCache), realpath: maybeBind(host, host.realpath), directoryExists: directoryName => { @@ -1361,6 +1362,10 @@ namespace ts { return host.readFile && host.readFile(fileName); } + function readFileBuffer(fileName: string): Uint8Array | undefined { + return host.readFileBuffer && host.readFileBuffer(fileName); + } + // Release any files we have acquired in the old program but are // not part of the new program. function onReleaseOldSourceFile(oldSourceFile: SourceFile, oldOptions: CompilerOptions) { diff --git a/src/services/shims.ts b/src/services/shims.ts index 8729ce6cc0bb6..d9c106e6a858b 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -104,6 +104,7 @@ namespace ts { * Read arbitrary text files on disk, i.e. when resolution procedure needs the content of 'package.json' to determine location of bundled typings for node modules */ readFile(fileName: string): string | undefined; + readFileBuffer(path: string): Uint8Array | undefined; realpath?(path: string): string; trace(s: string): void; useCaseSensitiveFileNames?(): boolean; @@ -541,6 +542,10 @@ namespace ts { return this.shimHost.readFile(fileName); } + public readFileBuffer(path: string): Uint8Array | undefined { + return this.shimHost.readFileBuffer(path); + } + public getDirectories(path: string): string[] { return JSON.parse(this.shimHost.getDirectories(path)); } diff --git a/src/services/transpile.ts b/src/services/transpile.ts index 103c7dc503bbb..50a529d87440d 100644 --- a/src/services/transpile.ts +++ b/src/services/transpile.ts @@ -83,6 +83,7 @@ namespace ts { getNewLine: () => newLine, fileExists: (fileName): boolean => fileName === inputFileName, readFile: () => "", + readFileBuffer: () => new Uint8Array(0), directoryExists: () => true, getDirectories: () => [] }; diff --git a/src/services/types.ts b/src/services/types.ts index d4a8448bc9401..0b684e9548298 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -248,6 +248,7 @@ namespace ts { */ readDirectory?(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[]; readFile?(path: string, encoding?: string): string | undefined; + readFileBuffer?(path: string): Uint8Array | undefined; realpath?(path: string): string; fileExists?(path: string): boolean; diff --git a/src/testRunner/rwcRunner.ts b/src/testRunner/rwcRunner.ts index 29e86382aff34..2c9ba4c24f863 100644 --- a/src/testRunner/rwcRunner.ts +++ b/src/testRunner/rwcRunner.ts @@ -74,7 +74,8 @@ namespace RWC { useCaseSensitiveFileNames: Harness.IO.useCaseSensitiveFileNames(), fileExists: Harness.IO.fileExists, readDirectory: Harness.IO.readDirectory, - readFile: Harness.IO.readFile + readFile: Harness.IO.readFile, + readFileBuffer: Harness.IO.readFileBuffer, }; const configParseResult = ts.parseJsonSourceFileConfigFileContent(parsedTsconfigFileContents, configParseHost, ts.getDirectoryPath(tsconfigFile.path)); fileNames = configParseResult.fileNames; diff --git a/src/testRunner/unittests/config/showConfig.ts b/src/testRunner/unittests/config/showConfig.ts index f8905e3412677..89909a5c35ccb 100644 --- a/src/testRunner/unittests/config/showConfig.ts +++ b/src/testRunner/unittests/config/showConfig.ts @@ -19,6 +19,7 @@ namespace ts { readDirectory() { return []; }, readFile: path => comparePaths(getNormalizedAbsolutePath(path, cwd), configPath) === Comparison.EqualTo ? configContents : undefined, + readFileBuffer: () => undefined, }; let commandLine = parseCommandLine(commandLinesArgs); if (commandLine.options.project) { diff --git a/src/testRunner/unittests/customTransforms.ts b/src/testRunner/unittests/customTransforms.ts index 7992f8a9c559f..69552d87e6153 100644 --- a/src/testRunner/unittests/customTransforms.ts +++ b/src/testRunner/unittests/customTransforms.ts @@ -15,6 +15,7 @@ namespace ts { getNewLine: () => "\n", fileExists: (fileName) => fileMap.has(fileName), readFile: (fileName) => fileMap.has(fileName) ? fileMap.get(fileName)!.text : undefined, + readFileBuffer: () => undefined, writeFile: (fileName, text) => outputs.set(fileName, text), }; diff --git a/src/testRunner/unittests/moduleResolution.ts b/src/testRunner/unittests/moduleResolution.ts index 477952cc4004f..2db77bc579161 100644 --- a/src/testRunner/unittests/moduleResolution.ts +++ b/src/testRunner/unittests/moduleResolution.ts @@ -31,6 +31,7 @@ namespace ts { interface File { name: string; content?: string; + buffer?: Uint8Array; symlinks?: string[]; } @@ -60,6 +61,7 @@ namespace ts { } return { readFile, + readFileBuffer, realpath, directoryExists: path => directories.has(path), fileExists: path => { @@ -69,12 +71,16 @@ namespace ts { }; } else { - return { readFile, realpath, fileExists: path => map.has(path) }; + return { readFile, readFileBuffer, realpath, fileExists: path => map.has(path) }; } function readFile(path: string): string | undefined { const file = map.get(path); return file && file.content; } + function readFileBuffer(path: string): Uint8Array | undefined { + const file = map.get(path); + return file && file.buffer; + } function realpath(path: string): string { return map.get(path)!.name; } @@ -478,6 +484,7 @@ namespace ts { return files.has(path); }, readFile: notImplemented, + readFileBuffer: notImplemented, }; const program = createProgram(rootFiles, options, host); @@ -573,6 +580,7 @@ export = C; return files.has(path); }, readFile: notImplemented, + readFileBuffer: notImplemented, }; const program = createProgram(rootFiles, options, host); const diagnostics = sortAndDeduplicateDiagnostics([...program.getSemanticDiagnostics(), ...program.getOptionsDiagnostics()]); @@ -1265,6 +1273,7 @@ import b = require("./moduleB"); it("No 'fileExists' calls if containing directory is missing", () => { const host: ModuleResolutionHost = { readFile: notImplemented, + readFileBuffer: notImplemented, fileExists: notImplemented, directoryExists: _ => false }; @@ -1382,6 +1391,7 @@ import b = require("./moduleB"); const file = sourceFiles.get(fileName); return file && file.text; }, + readFileBuffer: notImplemented, }; const program1 = createProgram(names, {}, compilerHost); const diagnostics1 = program1.getFileProcessingDiagnostics().getDiagnostics(); @@ -1418,6 +1428,7 @@ import b = require("./moduleB"); getNewLine: () => "\r\n", useCaseSensitiveFileNames: () => false, readFile: fileName => fileName === file.fileName ? file.text : undefined, + readFileBuffer: notImplemented, resolveModuleNames: notImplemented, }; createProgram([f.name], {}, compilerHost); @@ -1447,6 +1458,7 @@ import b = require("./moduleB"); getNewLine: () => "\r\n", useCaseSensitiveFileNames: () => false, readFile: fileName => fileName === file.fileName ? file.text : undefined, + readFileBuffer: notImplemented, resolveModuleNames(moduleNames: string[], _containingFile: string) { assert.deepEqual(moduleNames, ["fs"]); return [undefined!]; // TODO: GH#18217 diff --git a/src/testRunner/unittests/programApi.ts b/src/testRunner/unittests/programApi.ts index 63c33deb59560..66013b3e0142f 100644 --- a/src/testRunner/unittests/programApi.ts +++ b/src/testRunner/unittests/programApi.ts @@ -116,6 +116,7 @@ namespace ts { useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames, fileExists: fileName => fileName === "test.ts", readFile: fileName => fileName === "test.ts" ? testSource : undefined, + readFileBuffer: () => undefined, resolveModuleNames: (_moduleNames: string[], _containingFile: string) => { throw new Error("unsupported"); }, getDirectories: _path => { throw new Error("unsupported"); }, }; diff --git a/src/testRunner/unittests/reuseProgramStructure.ts b/src/testRunner/unittests/reuseProgramStructure.ts index 8c3f9d6a94fb7..62e209874d0b0 100644 --- a/src/testRunner/unittests/reuseProgramStructure.ts +++ b/src/testRunner/unittests/reuseProgramStructure.ts @@ -136,6 +136,7 @@ namespace ts { const file = files.get(fileName); return file && file.text; }, + readFileBuffer: notImplemented, }; if (useGetSourceFileByPath) { const filesByPath = mapEntries(files, (fileName, file) => [toPath(fileName, "", getCanonicalFileName), file]); diff --git a/src/testRunner/unittests/tsserver/session.ts b/src/testRunner/unittests/tsserver/session.ts index 8084b79056b30..189d3c4a0cb27 100644 --- a/src/testRunner/unittests/tsserver/session.ts +++ b/src/testRunner/unittests/tsserver/session.ts @@ -9,6 +9,7 @@ namespace ts.server { useCaseSensitiveFileNames: true, write(s): void { lastWrittenToHost = s; }, readFile: returnUndefined, + readFileBuffer: returnUndefined, writeFile: noop, resolvePath(): string { return undefined!; }, // TODO: GH#18217 fileExists: () => false, diff --git a/tests/baselines/reference/APISample_WatchWithOwnWatchHost.js b/tests/baselines/reference/APISample_WatchWithOwnWatchHost.js index 75444a614f952..12e769c48bfd5 100644 --- a/tests/baselines/reference/APISample_WatchWithOwnWatchHost.js +++ b/tests/baselines/reference/APISample_WatchWithOwnWatchHost.js @@ -31,6 +31,7 @@ function watchMain() { fileExists: ts.sys.fileExists, readFile: ts.sys.readFile, + readFileBuffer: ts.sys.readFileBuffer, directoryExists: ts.sys.directoryExists, getDirectories: ts.sys.getDirectories, readDirectory: ts.sys.readDirectory, @@ -83,6 +84,7 @@ function watchMain() { getDefaultLibFileName: function (options) { return ts.getDefaultLibFilePath(options); }, fileExists: ts.sys.fileExists, readFile: ts.sys.readFile, + readFileBuffer: ts.sys.readFileBuffer, directoryExists: ts.sys.directoryExists, getDirectories: ts.sys.getDirectories, readDirectory: ts.sys.readDirectory, diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 16f62ef389033..e70d003958db3 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2003,6 +2003,7 @@ declare namespace ts { */ fileExists(path: string): boolean; readFile(path: string): string | undefined; + readFileBuffer(path: string): Uint8Array | undefined; trace?(s: string): void; } /** @@ -2767,6 +2768,7 @@ declare namespace ts { emitBOM?: boolean; emitDecoratorMetadata?: boolean; experimentalDecorators?: boolean; + experimentalWasmModules?: boolean; forceConsistentCasingInFileNames?: boolean; importHelpers?: boolean; importsNotUsedAsValues?: ImportsNotUsedAsValues; @@ -2895,7 +2897,8 @@ declare namespace ts { * Used on extensions that doesn't define the ScriptKind but the content defines it. * Deferred extensions are going to be included in all project contexts. */ - Deferred = 7 + Deferred = 7, + Wasm = 8 } export enum ScriptTarget { ES3 = 0, @@ -2945,6 +2948,7 @@ declare namespace ts { export interface ModuleResolutionHost { fileExists(fileName: string): boolean; readFile(fileName: string): string | undefined; + readFileBuffer(fileName: string): Uint8Array | undefined; trace?(s: string): void; directoryExists?(directoryName: string): boolean; /** @@ -3007,7 +3011,8 @@ declare namespace ts { Js = ".js", Jsx = ".jsx", Json = ".json", - TsBuildInfo = ".tsbuildinfo" + TsBuildInfo = ".tsbuildinfo", + Wasm = ".wasm" } export interface ResolvedModuleWithFailedLookupLocations { readonly resolvedModule: ResolvedModuleFull | undefined; @@ -3691,7 +3696,7 @@ declare namespace ts { * }); * ``` */ - onEmitNode?(hint: EmitHint, node: Node | undefined, emitCallback: (hint: EmitHint, node: Node | undefined) => void): void; + onEmitNode?(hint: EmitHint, node: Node | undefined, emitCallback: (hint: EmitHint, node: Node | undefined) => void, getTextPos: () => number): void; /** * A hook used to check if an emit notification is required for a node. * @param node The node to emit. @@ -3844,6 +3849,7 @@ declare namespace ts { write(s: string): void; writeOutputIsTTY?(): boolean; readFile(path: string, encoding?: string): string | undefined; + readFileBuffer(path: string): Uint8Array | undefined; getFileSize?(path: string): number; writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; /** @@ -4471,7 +4477,7 @@ declare namespace ts { * that they appear in the source code. The language service depends on this property to locate nodes by position. */ export function forEachChild(node: Node, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined; - export function createSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes?: boolean, scriptKind?: ScriptKind): SourceFile; + export function createSourceFile(fileName: string, sourceText: string | Uint8Array, languageVersion: ScriptTarget, setParentNodes?: boolean, scriptKind?: ScriptKind): SourceFile; export function parseIsolatedEntityName(text: string, languageVersion: ScriptTarget): EntityName | undefined; /** * Parse json text into SyntaxTree and return node and parse errors if any @@ -4612,6 +4618,99 @@ declare namespace ts { function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations; function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: NonRelativeModuleNameResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations; } +declare namespace ts.wasm { + interface WasmSourceFile { + readonly fileName: string; + readonly magic: [number, number, number, number]; + readonly version: number; + readonly sections: WasmSection[]; + } + enum SectionKind { + Custom = 0, + Type = 1, + Import = 2, + Function = 3, + Table = 4, + Memory = 5, + Global = 6, + Export = 7, + Start = 8, + Element = 9, + Code = 10, + Data = 11 + } + function parse(fileName: string, buf: Uint8Array): WasmSourceFile; + type WasmSection = CustomSection | TypeSection | ImportSection | FunctionSection | TableSection | MemorySection | GlobalSection | ExportSection | StartSection | ElementSection | CodeSection | DataSection; + interface SectionBase { + readonly id: SectionKind; + readonly start: number; + readonly size: number; + } + interface CustomSection extends SectionBase { + readonly id: SectionKind.Custom; + readonly name: string; + readonly bytes: Uint8Array; + } + interface TypeSection extends SectionBase { + readonly id: SectionKind.Type; + readonly funcs: [parameters: ValueType[], results: ValueType[]][]; + } + interface ImportSection extends SectionBase { + readonly id: SectionKind.Import; + } + interface FunctionSection extends SectionBase { + readonly id: SectionKind.Function; + readonly indices: number[]; + } + interface TableSection extends SectionBase { + readonly id: SectionKind.Table; + } + interface MemorySection extends SectionBase { + readonly id: SectionKind.Memory; + } + interface GlobalSection extends SectionBase { + readonly id: SectionKind.Global; + } + interface ExportSection extends SectionBase { + readonly id: SectionKind.Export; + readonly exports: WasmExport[]; + } + interface StartSection extends SectionBase { + readonly id: SectionKind.Start; + } + interface ElementSection extends SectionBase { + readonly id: SectionKind.Element; + } + interface CodeSection extends SectionBase { + readonly id: SectionKind.Code; + } + interface DataSection extends SectionBase { + readonly id: SectionKind.Data; + } + enum ValueType { + i32 = 127, + i64 = 126, + f32 = 125, + f64 = 124 + } + interface WasmExport { + readonly name: string; + readonly exportdesc: ExportDescription; + } + enum ExportKind { + Func = 0, + Table = 1, + Mem = 2, + Global = 3 + } + interface ExportDescription { + readonly kind: ExportKind; + readonly index: number; + } +} +declare namespace ts.wasm { + function declarationsFor(file: WasmSourceFile): SourceFile; +} declare namespace ts { /** * Visits a Node using the supplied visitor, possibly returning a new Node in its place. @@ -4945,6 +5044,7 @@ declare namespace ts { * if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well */ readFile(path: string, encoding?: string): string | undefined; + readFileBuffer(path: string): Uint8Array | undefined; /** If provided, used for module resolution as well as to handle directory structure */ directoryExists?(path: string): boolean; /** If provided, used in resolutions as well as handling directory structure */ @@ -5332,6 +5432,7 @@ declare namespace ts { useCaseSensitiveFileNames?(): boolean; readDirectory?(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[]; readFile?(path: string, encoding?: string): string | undefined; + readFileBuffer?(path: string): Uint8Array | undefined; realpath?(path: string): string; fileExists?(path: string): boolean; getTypeRootsVersion?(): number; @@ -6163,7 +6264,8 @@ declare namespace ts { tsxModifier = ".tsx", jsModifier = ".js", jsxModifier = ".jsx", - jsonModifier = ".json" + jsonModifier = ".json", + wasmModifier = ".wasm" } enum ClassificationTypeNames { comment = "comment", @@ -9172,6 +9274,7 @@ declare namespace ts.server { useCaseSensitiveFileNames(): boolean; readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[]; readFile(fileName: string): string | undefined; + readFileBuffer(path: string): Uint8Array | undefined; writeFile(fileName: string, content: string): void; fileExists(file: string): boolean; resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference): (ResolvedModuleFull | undefined)[]; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 76296ad0927b8..c5d349144b485 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2003,6 +2003,7 @@ declare namespace ts { */ fileExists(path: string): boolean; readFile(path: string): string | undefined; + readFileBuffer(path: string): Uint8Array | undefined; trace?(s: string): void; } /** @@ -2767,6 +2768,7 @@ declare namespace ts { emitBOM?: boolean; emitDecoratorMetadata?: boolean; experimentalDecorators?: boolean; + experimentalWasmModules?: boolean; forceConsistentCasingInFileNames?: boolean; importHelpers?: boolean; importsNotUsedAsValues?: ImportsNotUsedAsValues; @@ -2895,7 +2897,8 @@ declare namespace ts { * Used on extensions that doesn't define the ScriptKind but the content defines it. * Deferred extensions are going to be included in all project contexts. */ - Deferred = 7 + Deferred = 7, + Wasm = 8 } export enum ScriptTarget { ES3 = 0, @@ -2945,6 +2948,7 @@ declare namespace ts { export interface ModuleResolutionHost { fileExists(fileName: string): boolean; readFile(fileName: string): string | undefined; + readFileBuffer(fileName: string): Uint8Array | undefined; trace?(s: string): void; directoryExists?(directoryName: string): boolean; /** @@ -3007,7 +3011,8 @@ declare namespace ts { Js = ".js", Jsx = ".jsx", Json = ".json", - TsBuildInfo = ".tsbuildinfo" + TsBuildInfo = ".tsbuildinfo", + Wasm = ".wasm" } export interface ResolvedModuleWithFailedLookupLocations { readonly resolvedModule: ResolvedModuleFull | undefined; @@ -3691,7 +3696,7 @@ declare namespace ts { * }); * ``` */ - onEmitNode?(hint: EmitHint, node: Node | undefined, emitCallback: (hint: EmitHint, node: Node | undefined) => void): void; + onEmitNode?(hint: EmitHint, node: Node | undefined, emitCallback: (hint: EmitHint, node: Node | undefined) => void, getTextPos: () => number): void; /** * A hook used to check if an emit notification is required for a node. * @param node The node to emit. @@ -3844,6 +3849,7 @@ declare namespace ts { write(s: string): void; writeOutputIsTTY?(): boolean; readFile(path: string, encoding?: string): string | undefined; + readFileBuffer(path: string): Uint8Array | undefined; getFileSize?(path: string): number; writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; /** @@ -4471,7 +4477,7 @@ declare namespace ts { * that they appear in the source code. The language service depends on this property to locate nodes by position. */ export function forEachChild(node: Node, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined; - export function createSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes?: boolean, scriptKind?: ScriptKind): SourceFile; + export function createSourceFile(fileName: string, sourceText: string | Uint8Array, languageVersion: ScriptTarget, setParentNodes?: boolean, scriptKind?: ScriptKind): SourceFile; export function parseIsolatedEntityName(text: string, languageVersion: ScriptTarget): EntityName | undefined; /** * Parse json text into SyntaxTree and return node and parse errors if any @@ -4612,6 +4618,99 @@ declare namespace ts { function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations; function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: NonRelativeModuleNameResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations; } +declare namespace ts.wasm { + interface WasmSourceFile { + readonly fileName: string; + readonly magic: [number, number, number, number]; + readonly version: number; + readonly sections: WasmSection[]; + } + enum SectionKind { + Custom = 0, + Type = 1, + Import = 2, + Function = 3, + Table = 4, + Memory = 5, + Global = 6, + Export = 7, + Start = 8, + Element = 9, + Code = 10, + Data = 11 + } + function parse(fileName: string, buf: Uint8Array): WasmSourceFile; + type WasmSection = CustomSection | TypeSection | ImportSection | FunctionSection | TableSection | MemorySection | GlobalSection | ExportSection | StartSection | ElementSection | CodeSection | DataSection; + interface SectionBase { + readonly id: SectionKind; + readonly start: number; + readonly size: number; + } + interface CustomSection extends SectionBase { + readonly id: SectionKind.Custom; + readonly name: string; + readonly bytes: Uint8Array; + } + interface TypeSection extends SectionBase { + readonly id: SectionKind.Type; + readonly funcs: [parameters: ValueType[], results: ValueType[]][]; + } + interface ImportSection extends SectionBase { + readonly id: SectionKind.Import; + } + interface FunctionSection extends SectionBase { + readonly id: SectionKind.Function; + readonly indices: number[]; + } + interface TableSection extends SectionBase { + readonly id: SectionKind.Table; + } + interface MemorySection extends SectionBase { + readonly id: SectionKind.Memory; + } + interface GlobalSection extends SectionBase { + readonly id: SectionKind.Global; + } + interface ExportSection extends SectionBase { + readonly id: SectionKind.Export; + readonly exports: WasmExport[]; + } + interface StartSection extends SectionBase { + readonly id: SectionKind.Start; + } + interface ElementSection extends SectionBase { + readonly id: SectionKind.Element; + } + interface CodeSection extends SectionBase { + readonly id: SectionKind.Code; + } + interface DataSection extends SectionBase { + readonly id: SectionKind.Data; + } + enum ValueType { + i32 = 127, + i64 = 126, + f32 = 125, + f64 = 124 + } + interface WasmExport { + readonly name: string; + readonly exportdesc: ExportDescription; + } + enum ExportKind { + Func = 0, + Table = 1, + Mem = 2, + Global = 3 + } + interface ExportDescription { + readonly kind: ExportKind; + readonly index: number; + } +} +declare namespace ts.wasm { + function declarationsFor(file: WasmSourceFile): SourceFile; +} declare namespace ts { /** * Visits a Node using the supplied visitor, possibly returning a new Node in its place. @@ -4945,6 +5044,7 @@ declare namespace ts { * if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well */ readFile(path: string, encoding?: string): string | undefined; + readFileBuffer(path: string): Uint8Array | undefined; /** If provided, used for module resolution as well as to handle directory structure */ directoryExists?(path: string): boolean; /** If provided, used in resolutions as well as handling directory structure */ @@ -5332,6 +5432,7 @@ declare namespace ts { useCaseSensitiveFileNames?(): boolean; readDirectory?(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[]; readFile?(path: string, encoding?: string): string | undefined; + readFileBuffer?(path: string): Uint8Array | undefined; realpath?(path: string): string; fileExists?(path: string): boolean; getTypeRootsVersion?(): number; @@ -6163,7 +6264,8 @@ declare namespace ts { tsxModifier = ".tsx", jsModifier = ".js", jsxModifier = ".jsx", - jsonModifier = ".json" + jsonModifier = ".json", + wasmModifier = ".wasm" } enum ClassificationTypeNames { comment = "comment", diff --git a/tests/baselines/reference/showConfig/Shows tsconfig for single option/experimentalWasmModules/tsconfig.json b/tests/baselines/reference/showConfig/Shows tsconfig for single option/experimentalWasmModules/tsconfig.json new file mode 100644 index 0000000000000..6d5a9b65cf096 --- /dev/null +++ b/tests/baselines/reference/showConfig/Shows tsconfig for single option/experimentalWasmModules/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "experimentalWasmModules": true + } +} diff --git a/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json b/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json index c9f603c2199ec..de9b2049d4f1d 100644 --- a/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json @@ -61,6 +61,7 @@ /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + // "experimentalWasmModules": true, /* Enables experimental loading of type information from experimental wasm modules. */ /* Advanced Options */ "skipLibCheck": true, /* Skip type checking of declaration files. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with advanced options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with advanced options/tsconfig.json index 83893736515ea..87e61053e1e3c 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with advanced options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with advanced options/tsconfig.json @@ -61,6 +61,7 @@ /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + // "experimentalWasmModules": true, /* Enables experimental loading of type information from experimental wasm modules. */ /* Advanced Options */ "noErrorTruncation": true, /* Do not truncate error messages. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json index f2ff8ef9f0053..75ab28ed96562 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json @@ -61,6 +61,7 @@ /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + // "experimentalWasmModules": true, /* Enables experimental loading of type information from experimental wasm modules. */ /* Advanced Options */ "skipLibCheck": true, /* Skip type checking of declaration files. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json index 286219dae6e96..95a8b5c05600f 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json @@ -61,6 +61,7 @@ /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + // "experimentalWasmModules": true, /* Enables experimental loading of type information from experimental wasm modules. */ /* Advanced Options */ "skipLibCheck": true, /* Skip type checking of declaration files. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json index 0ec33da9842fc..c9f15746538e2 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json @@ -61,6 +61,7 @@ /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + // "experimentalWasmModules": true, /* Enables experimental loading of type information from experimental wasm modules. */ /* Advanced Options */ "skipLibCheck": true, /* Skip type checking of declaration files. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json index ea8f60ca0cf8c..2171b73261d7b 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json @@ -61,6 +61,7 @@ /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + // "experimentalWasmModules": true, /* Enables experimental loading of type information from experimental wasm modules. */ /* Advanced Options */ "skipLibCheck": true, /* Skip type checking of declaration files. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json index c9f603c2199ec..de9b2049d4f1d 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json @@ -61,6 +61,7 @@ /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + // "experimentalWasmModules": true, /* Enables experimental loading of type information from experimental wasm modules. */ /* Advanced Options */ "skipLibCheck": true, /* Skip type checking of declaration files. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json index 7add8d543f8c8..4ffbd02311d5f 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json @@ -61,6 +61,7 @@ /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + // "experimentalWasmModules": true, /* Enables experimental loading of type information from experimental wasm modules. */ /* Advanced Options */ "skipLibCheck": true, /* Skip type checking of declaration files. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json index 29a0374fa2688..014aca1a45dd4 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json @@ -61,6 +61,7 @@ /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + // "experimentalWasmModules": true, /* Enables experimental loading of type information from experimental wasm modules. */ /* Advanced Options */ "skipLibCheck": true, /* Skip type checking of declaration files. */ diff --git a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/declarationDir-is-specified.js b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/declarationDir-is-specified.js index 68e55e69a289b..dc30e061b271e 100644 --- a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/declarationDir-is-specified.js +++ b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/declarationDir-is-specified.js @@ -82,6 +82,7 @@ interface Array { length: number; [n: number]: T; } /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + // "experimentalWasmModules": true, /* Enables experimental loading of type information from experimental wasm modules. */ /* Advanced Options */ "declarationDir": "decls", /* Output directory for generated declaration files. */ diff --git a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-and-declarationDir-is-specified.js b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-and-declarationDir-is-specified.js index 4f58a6cb9209c..e8b529176c7b4 100644 --- a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-and-declarationDir-is-specified.js +++ b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-and-declarationDir-is-specified.js @@ -82,6 +82,7 @@ interface Array { length: number; [n: number]: T; } /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + // "experimentalWasmModules": true, /* Enables experimental loading of type information from experimental wasm modules. */ /* Advanced Options */ "declarationDir": "decls", /* Output directory for generated declaration files. */ diff --git a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-is-specified.js b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-is-specified.js index c0390a97918af..389bff58ec190 100644 --- a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-is-specified.js +++ b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/when-outDir-is-specified.js @@ -82,6 +82,7 @@ interface Array { length: number; [n: number]: T; } /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + // "experimentalWasmModules": true, /* Enables experimental loading of type information from experimental wasm modules. */ /* Advanced Options */ "skipLibCheck": true, /* Skip type checking of declaration files. */ diff --git a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/with-outFile.js b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/with-outFile.js index b04d25e8fefc4..c4107e0bb4e7e 100644 --- a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/with-outFile.js +++ b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/with-outFile.js @@ -82,6 +82,7 @@ interface Array { length: number; [n: number]: T; } /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + // "experimentalWasmModules": true, /* Enables experimental loading of type information from experimental wasm modules. */ /* Advanced Options */ "skipLibCheck": true, /* Skip type checking of declaration files. */ diff --git a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified-with-declaration-enabled.js b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified-with-declaration-enabled.js index a95b9006f3cbf..7374ca076a31e 100644 --- a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified-with-declaration-enabled.js +++ b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified-with-declaration-enabled.js @@ -82,6 +82,7 @@ interface Array { length: number; [n: number]: T; } /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + // "experimentalWasmModules": true, /* Enables experimental loading of type information from experimental wasm modules. */ /* Advanced Options */ "skipLibCheck": true, /* Skip type checking of declaration files. */ diff --git a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified.js b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified.js index 3b98f66e02fde..617948b27cab4 100644 --- a/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified.js +++ b/tests/baselines/reference/tscWatch/programUpdates/should-not-trigger-recompilation-because-of-program-emit/without-outDir-or-outFile-is-specified.js @@ -82,6 +82,7 @@ interface Array { length: number; [n: number]: T; } /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + // "experimentalWasmModules": true, /* Enables experimental loading of type information from experimental wasm modules. */ /* Advanced Options */ "skipLibCheck": true, /* Skip type checking of declaration files. */ diff --git a/tests/cases/compiler/APISample_WatchWithOwnWatchHost.ts b/tests/cases/compiler/APISample_WatchWithOwnWatchHost.ts index 18cb5766ce331..c9638532f41cf 100644 --- a/tests/cases/compiler/APISample_WatchWithOwnWatchHost.ts +++ b/tests/cases/compiler/APISample_WatchWithOwnWatchHost.ts @@ -35,6 +35,7 @@ function watchMain() { fileExists: ts.sys.fileExists, readFile: ts.sys.readFile, + readFileBuffer: ts.sys.readFileBuffer, directoryExists: ts.sys.directoryExists, getDirectories: ts.sys.getDirectories, readDirectory: ts.sys.readDirectory, From 119e2bdbb0d518f4ac0a233e72e6836502bfc8a6 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 28 Jul 2020 01:28:30 -0700 Subject: [PATCH 3/7] Be slightly more charitable with unhandled vs unimplemented export kinds --- src/compiler/wasm/transform.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/compiler/wasm/transform.ts b/src/compiler/wasm/transform.ts index 3e4047540eabb..88c8339c26bfc 100644 --- a/src/compiler/wasm/transform.ts +++ b/src/compiler/wasm/transform.ts @@ -23,7 +23,7 @@ namespace ts.wasm { /*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, - finishNode(factory.createNamedExports([]))))] : map(exports.exports, createExportedStatement); + finishNode(factory.createNamedExports([]))))] : mapDefined(exports.exports, createExportedStatement); const eofToken = factory.createToken(SyntaxKind.EndOfFileToken); const result = factory.createSourceFile(statements, eofToken, NodeFlags.Ambient); result.referencedFiles = emptyArray; @@ -77,7 +77,7 @@ namespace ts.wasm { return node; } - function createExportedStatement(e: WasmExport): Statement { + function createExportedStatement(e: WasmExport): Statement | undefined { switch(e.exportdesc.kind) { case ExportKind.Func: { const funcTypeSection = getSection(file, SectionKind.Type); @@ -107,8 +107,9 @@ namespace ts.wasm { case ExportKind.Global: case ExportKind.Mem: case ExportKind.Table: + return undefined; default: - return Debug.fail("Unhandled export kind"); + return Debug.fail(`Unhandled wasm export kind: ${e.exportdesc.kind}`); } } } From e4766919d6670a7d831fc0f83c7fa16e969cd8d1 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 28 Jul 2020 01:47:50 -0700 Subject: [PATCH 4/7] Handle buffers throughout script snapshots --- src/server/scriptInfo.ts | 6 +++++- src/server/scriptVersionCache.ts | 4 ++++ src/services/services.ts | 2 +- src/services/shims.ts | 2 ++ src/services/types.ts | 15 ++++++++++++--- src/services/utilities.ts | 4 ++++ 6 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index e362256b46b5e..24a060e4b6abf 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -20,6 +20,7 @@ namespace ts.server { * Only on edits to the script version cache, the text will be set to undefined */ private text: string | undefined; + private buffer: Uint8Array | undefined; /** * Line map for the text when there is no script version cache present */ @@ -164,7 +165,7 @@ namespace ts.server { public getSnapshot(): IScriptSnapshot { return this.useScriptVersionCacheIfValidOrOpen() ? this.svc!.getSnapshot() - : ScriptSnapshot.fromString(this.getOrLoadText()); + : ScriptSnapshot.fromString(this.getOrLoadText(), this.buffer); } public getAbsolutePositionAndLineText(line: number): AbsolutePositionAndLineText { @@ -208,6 +209,9 @@ namespace ts.server { let text: string; const fileName = tempFileName || this.info.fileName; const getText = () => text === undefined ? (text = this.host.readFile(fileName) || "") : text; + if (endsWith(fileName, ".wasm")) { + this.buffer = this.host.readFileBuffer(fileName); + } // Only non typescript files have size limitation if (!hasTSFileExtension(this.info.fileName)) { const fileSize = this.host.getFileSize ? this.host.getFileSize(fileName) : getText().length; diff --git a/src/server/scriptVersionCache.ts b/src/server/scriptVersionCache.ts index 3daa9968a2f52..7f19540f43596 100644 --- a/src/server/scriptVersionCache.ts +++ b/src/server/scriptVersionCache.ts @@ -370,6 +370,10 @@ namespace ts.server { return this.index.getText(rangeStart, rangeEnd - rangeStart); } + getBuffer() { + return ts.sys.bufferFrom?.(this.getText(0, this.index.getLength()), "utf8") as Uint8Array; // TODO: Very unsafe, discarding `undefined` + } + getLength() { return this.index.getLength(); } diff --git a/src/services/services.ts b/src/services/services.ts index 9a3dc81677b92..4b7f967c86660 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1066,7 +1066,7 @@ namespace ts { } export function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean, scriptKind?: ScriptKind): SourceFile { - const sourceFile = createSourceFile(fileName, getSnapshotText(scriptSnapshot), scriptTarget, setNodeParents, scriptKind); + const sourceFile = createSourceFile(fileName, endsWith(fileName, ".wasm") ? getSnapshotBuffer(scriptSnapshot) : getSnapshotText(scriptSnapshot), scriptTarget, setNodeParents, scriptKind); setSourceFileFields(sourceFile, scriptSnapshot, version); return sourceFile; } diff --git a/src/services/shims.ts b/src/services/shims.ts index d9c106e6a858b..d190bf8020d56 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -312,6 +312,8 @@ namespace ts { return this.scriptSnapshotShim.getText(start, end); } + getBuffer = notImplemented; + public getLength(): number { return this.scriptSnapshotShim.getLength(); } diff --git a/src/services/types.ts b/src/services/types.ts index 0b684e9548298..e5928d6e2dbca 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -121,6 +121,11 @@ namespace ts { /** Gets a portion of the script snapshot specified by [start, end). */ getText(start: number, end: number): string; + /** + * Gets the udnerlying buffer for the snapshot + */ + getBuffer(): Uint8Array; + /** Gets the length of this script snapshot. */ getLength(): number; @@ -140,7 +145,7 @@ namespace ts { export namespace ScriptSnapshot { class StringScriptSnapshot implements IScriptSnapshot { - constructor(private text: string) { + constructor(private text: string, private buffer: Uint8Array | undefined) { } public getText(start: number, end: number): string { @@ -149,6 +154,10 @@ namespace ts { : this.text.substring(start, end); } + public getBuffer() { + return this.buffer || (ts.sys.bufferFrom?.(this.text, "utf8") as Uint8Array); + } + public getLength(): number { return this.text.length; } @@ -160,8 +169,8 @@ namespace ts { } } - export function fromString(text: string): IScriptSnapshot { - return new StringScriptSnapshot(text); + export function fromString(text: string, buffer?: Uint8Array): IScriptSnapshot { + return new StringScriptSnapshot(text, buffer); } } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index b3a09b1e5559b..9dbbd66539982 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1723,6 +1723,10 @@ namespace ts { return snap.getText(0, snap.getLength()); } + export function getSnapshotBuffer(snap: IScriptSnapshot): Uint8Array { + return snap.getBuffer(); + } + export function repeatString(str: string, count: number): string { let result = ""; for (let i = 0; i < count; i++) { From c53cf3c4d73d657bf78cabdf2a8310b32e9125e2 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 28 Jul 2020 02:30:41 -0700 Subject: [PATCH 5/7] Disable incremental LS operations over wasm files - they do _not_ work --- src/compiler/parser.ts | 4 ++-- src/services/documentRegistry.ts | 2 +- src/services/services.ts | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index e5e225ded4ac6..33b784f6b4987 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -825,7 +825,7 @@ namespace ts { return result; } else if (scriptKind === ScriptKind.Wasm) { - Debug.assert(!isString(sourceText)); + Debug.assert(!isString(sourceText), `Got string source for wasm module ${fileName}`); const result = wasm.declarationsFor(wasm.parse(fileName, sourceText)); if (setParentNodes) { fixupParentReferences(result); @@ -833,7 +833,7 @@ namespace ts { return result; } - Debug.assert(isString(sourceText)); + Debug.assert(isString(sourceText), `Got nonstring source for file ${fileName}`); initializeState(fileName, sourceText, languageVersion, syntaxCursor, scriptKind); const result = parseSourceFileWorker(languageVersion, setParentNodes, scriptKind); diff --git a/src/services/documentRegistry.ts b/src/services/documentRegistry.ts index 19fd476d87d1b..20d360a027831 100644 --- a/src/services/documentRegistry.ts +++ b/src/services/documentRegistry.ts @@ -185,7 +185,7 @@ namespace ts { } } - if (!entry) { + if (!entry || endsWith(fileName, ".wasm")) { // Have never seen this file with these settings. Create a new source file for it. const sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, scriptTarget, version, /*setNodeParents*/ false, scriptKind); if (externalCache) { diff --git a/src/services/services.ts b/src/services/services.ts index 4b7f967c86660..60595a00fd37e 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1038,7 +1038,7 @@ namespace ts { const version = this.host.getScriptVersion(fileName); let sourceFile: SourceFile | undefined; - if (this.currentFileName !== fileName) { + if (this.currentFileName !== fileName || scriptKind === ScriptKind.Wasm) { // This is a new file, just parse it sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, ScriptTarget.Latest, version, /*setNodeParents*/ true, scriptKind); } @@ -1074,7 +1074,7 @@ namespace ts { export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange | undefined, aggressiveChecks?: boolean): SourceFile { // If we were given a text change range, and our version or open-ness changed, then // incrementally parse this file. - if (textChangeRange) { + if (textChangeRange && !endsWith(sourceFile.fileName, ".wasm")) { if (version !== sourceFile.version) { let newText: string; @@ -1123,7 +1123,7 @@ namespace ts { } // Otherwise, just create a new source file. - return createLanguageServiceSourceFile(sourceFile.fileName, scriptSnapshot, sourceFile.languageVersion, version, /*setNodeParents*/ true, sourceFile.scriptKind); + return createLanguageServiceSourceFile(sourceFile.fileName, scriptSnapshot, sourceFile.languageVersion, version, /*setNodeParents*/ true, endsWith(sourceFile.fileName, ".wasm") ? ScriptKind.Wasm : sourceFile.scriptKind); } class CancellationTokenObject implements CancellationToken { @@ -1390,7 +1390,7 @@ namespace ts { // Check if the language version has changed since we last created a program; if they are the same, // it is safe to reuse the sourceFiles; if not, then the shape of the AST can change, and the oldSourceFile // can not be reused. we have to dump all syntax trees and create new ones. - if (!shouldCreateNewSourceFile) { + if (!shouldCreateNewSourceFile && !endsWith(fileName, ".wasm")) { // Check if the old program had this file already const oldSourceFile = program && program.getSourceFileByPath(path); if (oldSourceFile) { From 52838c3abcbd0c9270449d613ea6e87e95af09ee Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 28 Jul 2020 02:50:13 -0700 Subject: [PATCH 6/7] Fix tests again by accepting new baselines, including stub methods in unittests --- src/harness/harnessLanguageService.ts | 2 ++ src/testRunner/unittests/reuseProgramStructure.ts | 2 ++ tests/baselines/reference/api/tsserverlibrary.d.ts | 6 +++++- tests/baselines/reference/api/typescript.d.ts | 6 +++++- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 7d4a7e872345d..a9bd848358c1f 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -81,6 +81,8 @@ namespace Harness.LanguageService { return this.textSnapshot.substring(start, end); } + getBuffer = ts.notImplemented; + public getLength(): number { return this.textSnapshot.length; } diff --git a/src/testRunner/unittests/reuseProgramStructure.ts b/src/testRunner/unittests/reuseProgramStructure.ts index 62e209874d0b0..13dc871b84463 100644 --- a/src/testRunner/unittests/reuseProgramStructure.ts +++ b/src/testRunner/unittests/reuseProgramStructure.ts @@ -68,6 +68,8 @@ namespace ts { return this.getFullText().substring(start, end); } + getBuffer = notImplemented; + getLength(): number { return this.getFullText().length; } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index e70d003958db3..8f65a5029224b 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -5378,6 +5378,10 @@ declare namespace ts { interface IScriptSnapshot { /** Gets a portion of the script snapshot specified by [start, end). */ getText(start: number, end: number): string; + /** + * Gets the udnerlying buffer for the snapshot + */ + getBuffer(): Uint8Array; /** Gets the length of this script snapshot. */ getLength(): number; /** @@ -5392,7 +5396,7 @@ declare namespace ts { dispose?(): void; } namespace ScriptSnapshot { - function fromString(text: string): IScriptSnapshot; + function fromString(text: string, buffer?: Uint8Array): IScriptSnapshot; } interface PreProcessedFileInfo { referencedFiles: FileReference[]; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index c5d349144b485..275d4d0b08078 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -5378,6 +5378,10 @@ declare namespace ts { interface IScriptSnapshot { /** Gets a portion of the script snapshot specified by [start, end). */ getText(start: number, end: number): string; + /** + * Gets the udnerlying buffer for the snapshot + */ + getBuffer(): Uint8Array; /** Gets the length of this script snapshot. */ getLength(): number; /** @@ -5392,7 +5396,7 @@ declare namespace ts { dispose?(): void; } namespace ScriptSnapshot { - function fromString(text: string): IScriptSnapshot; + function fromString(text: string, buffer?: Uint8Array): IScriptSnapshot; } interface PreProcessedFileInfo { referencedFiles: FileReference[]; From 0f6b16e6046132988879f2ffa7e2703571324c2d Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 28 Jul 2020 02:55:30 -0700 Subject: [PATCH 7/7] Fix lints --- src/compiler/wasm/parser.ts | 6 +++--- src/harness/fakesHosts.ts | 4 ++-- src/server/scriptVersionCache.ts | 2 +- src/services/types.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/compiler/wasm/parser.ts b/src/compiler/wasm/parser.ts index fd4824c632da2..3e0a6e6690ec7 100644 --- a/src/compiler/wasm/parser.ts +++ b/src/compiler/wasm/parser.ts @@ -95,7 +95,7 @@ namespace ts.wasm { case ValueType.f32: case ValueType.f64: cursor.index++; - return byte as ValueType; + return byte; default: Debug.fail(`Unknown value type: ${byte}`); // TODO: Issue diagnostic } @@ -163,7 +163,7 @@ namespace ts.wasm { case ExportKind.Global: cursor.index++; const index = parseUnsignedLEB128u32(cursor); - return { kind: byte as ExportKind, index }; + return { kind: byte, index }; default: Debug.fail(`Unknown export kind: ${byte}`); // TODO: Issue diagnostic } @@ -217,7 +217,7 @@ namespace ts.wasm { return name; }, get bytes() { - this.name; + void this.name; // intializes bytes return bytes; } }; diff --git a/src/harness/fakesHosts.ts b/src/harness/fakesHosts.ts index 1bc47c7871ec9..6f6de8c01fdf5 100644 --- a/src/harness/fakesHosts.ts +++ b/src/harness/fakesHosts.ts @@ -43,7 +43,7 @@ namespace fakes { public readFile(path: string) { try { - let content = this.vfs.readFileSync(path, "utf8"); + const content = this.vfs.readFileSync(path, "utf8"); if (content === undefined) { return undefined; } @@ -56,7 +56,7 @@ namespace fakes { public readFileBuffer(path: string) { try { - let content = this.vfs.readFileSync(path); + const content = this.vfs.readFileSync(path); if (content === undefined) { return undefined; } diff --git a/src/server/scriptVersionCache.ts b/src/server/scriptVersionCache.ts index 7f19540f43596..1c5aaf0821055 100644 --- a/src/server/scriptVersionCache.ts +++ b/src/server/scriptVersionCache.ts @@ -371,7 +371,7 @@ namespace ts.server { } getBuffer() { - return ts.sys.bufferFrom?.(this.getText(0, this.index.getLength()), "utf8") as Uint8Array; // TODO: Very unsafe, discarding `undefined` + return sys.bufferFrom?.(this.getText(0, this.index.getLength()), "utf8") as Uint8Array; // TODO: Very unsafe, discarding `undefined` } getLength() { diff --git a/src/services/types.ts b/src/services/types.ts index e5928d6e2dbca..ef2ef086a28d0 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -155,7 +155,7 @@ namespace ts { } public getBuffer() { - return this.buffer || (ts.sys.bufferFrom?.(this.text, "utf8") as Uint8Array); + return this.buffer || (sys.bufferFrom?.(this.text, "utf8") as Uint8Array); // TODO: very unsafe, disreregards `undefined` result } public getLength(): number {