diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6d89c39c82fb9..537aa33c66b02 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19630,7 +19630,7 @@ namespace ts { function isNameOfModuleOrEnumDeclaration(node: Identifier) { const parent = node.parent; - return isModuleOrEnumDeclaration(parent) && node === parent.name; + return parent && isModuleOrEnumDeclaration(parent) && node === parent.name; } // When resolved as an expression identifier, if the given node references an exported entity, return the declaration diff --git a/src/compiler/core.ts b/src/compiler/core.ts index ac330b467c940..10509309e8035 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -571,7 +571,7 @@ namespace ts { */ export function append(to: T[] | undefined, value: T | undefined): T[] | undefined { if (value === undefined) return to; - if (to === undefined) to = []; + if (to === undefined) return [value]; to.push(value); return to; } @@ -592,6 +592,16 @@ namespace ts { return to; } + /** + * Stable sort of an array. Elements equal to each other maintain their relative position in the array. + */ + export function stableSort(array: T[], comparer: (x: T, y: T) => Comparison = compareValues) { + return array + .map((_, i) => i) // create array of indices + .sort((x, y) => comparer(array[x], array[y]) || compareValues(x, y)) // sort indices by value then position + .map(i => array[i]); // get sorted array + } + export function rangeEquals(array1: T[], array2: T[], pos: number, end: number) { while (pos < end) { if (array1[pos] !== array2[pos]) { @@ -2099,6 +2109,17 @@ namespace ts { } /** Remove an item from an array, moving everything to its right one space left. */ + export function orderedRemoveItem(array: T[], item: T): boolean { + for (let i = 0; i < array.length; i++) { + if (array[i] === item) { + orderedRemoveItemAt(array, i); + return true; + } + } + return false; + } + + /** Remove an item by index from an array, moving everything to its right one space left. */ export function orderedRemoveItemAt(array: T[], index: number): void { // This seems to be faster than either `array.splice(i, 1)` or `array.copyWithin(i, i+ 1)`. for (let i = index; i < array.length - 1; i++) { diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index eacfd1f0b678f..ed7f3ee16a0cf 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -20,188 +20,6 @@ namespace ts { export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile, emitOnlyDtsFiles?: boolean): EmitResult { const delimiters = createDelimiterMap(); const brackets = createBracketsMap(); - - // emit output for the __extends helper function - const extendsHelper = ` -var __extends = (this && this.__extends) || function (d, b) { - for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); -};`; - - // Emit output for the __assign helper function. - // This is typically used for JSX spread attributes, - // and can be used for object literal spread properties. - const assignHelper = ` -var __assign = (this && this.__assign) || Object.assign || function(t) { - for (var s, i = 1, n = arguments.length; i < n; i++) { - s = arguments[i]; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) - t[p] = s[p]; - if (typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) - t[p[i]] = s[p[i]]; - } - return t; -};`; - - const restHelper = ` -var __rest = (this && this.__rest) || function (s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0) - t[p[i]] = s[p[i]]; - return t; -};`; - - // emit output for the __decorate helper function - const decorateHelper = ` -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -};`; - - // emit output for the __metadata helper function - const metadataHelper = ` -var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); -};`; - - // emit output for the __param helper function - const paramHelper = ` -var __param = (this && this.__param) || function (paramIndex, decorator) { - return function (target, key) { decorator(target, key, paramIndex); } -};`; - - // emit output for the __awaiter helper function - const awaiterHelper = ` -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments)).next()); - }); -};`; - - // The __generator helper is used by down-level transformations to emulate the runtime - // semantics of an ES2015 generator function. When called, this helper returns an - // object that implements the Iterator protocol, in that it has `next`, `return`, and - // `throw` methods that step through the generator when invoked. - // - // parameters: - // thisArg The value to use as the `this` binding for the transformed generator body. - // body A function that acts as the transformed generator body. - // - // variables: - // _ Persistent state for the generator that is shared between the helper and the - // generator body. The state object has the following members: - // sent() - A method that returns or throws the current completion value. - // label - The next point at which to resume evaluation of the generator body. - // trys - A stack of protected regions (try/catch/finally blocks). - // ops - A stack of pending instructions when inside of a finally block. - // f A value indicating whether the generator is executing. - // y An iterator to delegate for a yield*. - // t A temporary variable that holds one of the following values (note that these - // cases do not overlap): - // - The completion value when resuming from a `yield` or `yield*`. - // - The error value for a catch block. - // - The current protected region (array of try/catch/finally/end labels). - // - The verb (`next`, `throw`, or `return` method) to delegate to the expression - // of a `yield*`. - // - The result of evaluating the verb delegated to the expression of a `yield*`. - // - // functions: - // verb(n) Creates a bound callback to the `step` function for opcode `n`. - // step(op) Evaluates opcodes in a generator body until execution is suspended or - // completed. - // - // The __generator helper understands a limited set of instructions: - // 0: next(value?) - Start or resume the generator with the specified value. - // 1: throw(error) - Resume the generator with an exception. If the generator is - // suspended inside of one or more protected regions, evaluates - // any intervening finally blocks between the current label and - // the nearest catch block or function boundary. If uncaught, the - // exception is thrown to the caller. - // 2: return(value?) - Resume the generator as if with a return. If the generator is - // suspended inside of one or more protected regions, evaluates any - // intervening finally blocks. - // 3: break(label) - Jump to the specified label. If the label is outside of the - // current protected region, evaluates any intervening finally - // blocks. - // 4: yield(value?) - Yield execution to the caller with an optional value. When - // resumed, the generator will continue at the next label. - // 5: yield*(value) - Delegates evaluation to the supplied iterator. When - // delegation completes, the generator will continue at the next - // label. - // 6: catch(error) - Handles an exception thrown from within the generator body. If - // the current label is inside of one or more protected regions, - // evaluates any intervening finally blocks between the current - // label and the nearest catch block or function boundary. If - // uncaught, the exception is thrown to the caller. - // 7: endfinally - Ends a finally block, resuming the last instruction prior to - // entering a finally block. - // - // For examples of how these are used, see the comments in ./transformers/generators.ts - const generatorHelper = ` -var __generator = (this && this.__generator) || function (thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t; - return { next: verb(0), "throw": verb(1), "return": verb(2) }; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (_) try { - if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [0, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -};`; - - // emit output for the __export helper function - const exportStarHelper = ` -function __export(m) { - for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; -}`; - - // emit output for the UMD helper function. - const umdHelper = ` -(function (dependencies, factory) { - if (typeof module === 'object' && typeof module.exports === 'object') { - var v = factory(require, exports); if (v !== undefined) module.exports = v; - } - else if (typeof define === 'function' && define.amd) { - define(dependencies, factory); - } -})`; - - const superHelper = ` -const _super = name => super[name];`; - - const advancedSuperHelper = ` -const _super = (function (geti, seti) { - const cache = Object.create(null); - return name => cache[name] || (cache[name] = { get value() { return geti(name); }, set value(v) { seti(name, v); } }); -})(name => super[name], (name, value) => super[name] = value);`; - const compilerOptions = host.getCompilerOptions(); const languageVersion = getEmitScriptTarget(compilerOptions); const moduleKind = getEmitModuleKind(compilerOptions); @@ -238,12 +56,7 @@ const _super = (function (geti, seti) { let currentSourceFile: SourceFile; let currentText: string; let currentFileIdentifiers: Map; - let extendsEmitted: boolean; - let assignEmitted: boolean; - let restEmitted: boolean; - let decorateEmitted: boolean; - let paramEmitted: boolean; - let awaiterEmitted: boolean; + let bundledHelpers: Map; let isOwnFileEmit: boolean; let emitSkipped = false; @@ -308,12 +121,13 @@ const _super = (function (geti, seti) { nodeIdToGeneratedName = []; autoGeneratedIdToGeneratedName = []; generatedNameSet = createMap(); + bundledHelpers = isBundledEmit ? createMap() : undefined; isOwnFileEmit = !isBundledEmit; // Emit helpers from all the files if (isBundledEmit && moduleKind) { for (const sourceFile of sourceFiles) { - emitEmitHelpers(sourceFile); + emitHelpers(sourceFile, /*isBundle*/ true); } } @@ -348,11 +162,6 @@ const _super = (function (geti, seti) { tempFlags = TempFlags.Auto; currentSourceFile = undefined; currentText = undefined; - extendsEmitted = false; - assignEmitted = false; - decorateEmitted = false; - paramEmitted = false; - awaiterEmitted = false; isOwnFileEmit = false; } @@ -861,6 +670,8 @@ const _super = (function (geti, seti) { // Transformation nodes case SyntaxKind.PartiallyEmittedExpression: return emitPartiallyEmittedExpression(node); + case SyntaxKind.RawExpression: + return writeLines((node).text); } } @@ -898,12 +709,7 @@ const _super = (function (geti, seti) { // function emitIdentifier(node: Identifier) { - if (getEmitFlags(node) & EmitFlags.UMDDefine) { - writeLines(umdHelper); - } - else { - write(getTextOfNode(node, /*includeTrivia*/ false)); - } + write(getTextOfNode(node, /*includeTrivia*/ false)); } // @@ -2207,93 +2013,39 @@ const _super = (function (geti, seti) { return statements.length; } - function emitHelpers(node: Node) { - const emitFlags = getEmitFlags(node); - let helpersEmitted = false; - if (emitFlags & EmitFlags.EmitEmitHelpers) { - helpersEmitted = emitEmitHelpers(currentSourceFile); - } - - if (emitFlags & EmitFlags.EmitExportStar) { - writeLines(exportStarHelper); - helpersEmitted = true; - } - - if (emitFlags & EmitFlags.EmitSuperHelper) { - writeLines(superHelper); - helpersEmitted = true; - } - - if (emitFlags & EmitFlags.EmitAdvancedSuperHelper) { - writeLines(advancedSuperHelper); - helpersEmitted = true; - } - - return helpersEmitted; - } - - function emitEmitHelpers(node: SourceFile) { - // Only emit helpers if the user did not say otherwise. - if (compilerOptions.noEmitHelpers) { - return false; - } - - // Don't emit helpers if we can import them. - if (compilerOptions.importHelpers - && (isExternalModule(node) || compilerOptions.isolatedModules)) { - return false; - } + function emitHelpers(node: Node, isBundle?: boolean) { + const sourceFile = isSourceFile(node) ? node : currentSourceFile; + const shouldSkip = compilerOptions.noEmitHelpers || (sourceFile && getExternalHelpersModuleName(sourceFile) !== undefined); + const shouldBundle = isSourceFile(node) && !isOwnFileEmit; let helpersEmitted = false; + const helpers = getEmitHelpers(node); + if (helpers) { + for (const helper of stableSort(helpers, compareEmitHelpers)) { + if (!helper.scoped) { + // Skip the helper if it can be skipped and the noEmitHelpers compiler + // option is set, or if it can be imported and the importHelpers compiler + // option is set. + if (shouldSkip) continue; + + // Skip the helper if it can be bundled but hasn't already been emitted and we + // are emitting a bundled module. + if (shouldBundle) { + if (bundledHelpers[helper.name]) { + continue; + } - // Only Emit __extends function when target ES5. - // For target ES6 and above, we can emit classDeclaration as is. - if ((languageVersion < ScriptTarget.ES2015) && (!extendsEmitted && node.flags & NodeFlags.HasClassExtends)) { - writeLines(extendsHelper); - extendsEmitted = true; - helpersEmitted = true; - } - - if ((languageVersion < ScriptTarget.ESNext || currentSourceFile.scriptKind === ScriptKind.JSX || currentSourceFile.scriptKind === ScriptKind.TSX) && - compilerOptions.jsx !== JsxEmit.Preserve && - !assignEmitted && - node.flags & NodeFlags.HasSpreadAttribute) { - writeLines(assignHelper); - assignEmitted = true; - } - - if (languageVersion < ScriptTarget.ESNext && !restEmitted && node.flags & NodeFlags.HasRestAttribute) { - writeLines(restHelper); - restEmitted = true; - } - - if (!decorateEmitted && node.flags & NodeFlags.HasDecorators) { - writeLines(decorateHelper); - if (compilerOptions.emitDecoratorMetadata) { - writeLines(metadataHelper); - } - - decorateEmitted = true; - helpersEmitted = true; - } - - if (!paramEmitted && node.flags & NodeFlags.HasParamDecorators) { - writeLines(paramHelper); - paramEmitted = true; - helpersEmitted = true; - } + bundledHelpers[helper.name] = true; + } + } + else if (isBundle) { + // Skip the helper if it is scoped and we are emitting bundled helpers + continue; + } - // Only emit __awaiter function when target ES5/ES6. - // Only emit __generator function when target ES5. - // For target ES2017 and above, we can emit async/await as is. - if ((languageVersion < ScriptTarget.ES2017) && (!awaiterEmitted && node.flags & NodeFlags.HasAsyncFunctions)) { - writeLines(awaiterHelper); - if (languageVersion < ScriptTarget.ES2015) { - writeLines(generatorHelper); + writeLines(helper.text); + helpersEmitted = true; } - - awaiterEmitted = true; - helpersEmitted = true; } if (helpersEmitted) { @@ -2304,9 +2056,10 @@ const _super = (function (geti, seti) { } function writeLines(text: string): void { - const lines = text.split(/\r\n|\r|\n/g); + const lines = text.split(/\r\n?|\n/g); + const indentation = guessIndentation(lines); for (let i = 0; i < lines.length; i++) { - const line = lines[i]; + const line = indentation ? lines[i].slice(indentation) : lines[i]; if (line.length) { if (i > 0) { writeLine(); @@ -2316,6 +2069,21 @@ const _super = (function (geti, seti) { } } + function guessIndentation(lines: string[]) { + let indentation: number; + for (const line of lines) { + for (let i = 0; i < line.length && (indentation === undefined || i < indentation); i++) { + if (!isWhiteSpace(line.charCodeAt(i))) { + if (indentation === undefined || i < indentation) { + indentation = i; + break; + } + } + } + } + return indentation; + } + // // Helpers // diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index c0f949c0ea9f5..d797fff92f4d5 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -1465,7 +1465,6 @@ namespace ts { if (node.resolvedTypeReferenceDirectiveNames !== undefined) updated.resolvedTypeReferenceDirectiveNames = node.resolvedTypeReferenceDirectiveNames; if (node.imports !== undefined) updated.imports = node.imports; if (node.moduleAugmentations !== undefined) updated.moduleAugmentations = node.moduleAugmentations; - if (node.externalHelpersModuleName !== undefined) updated.externalHelpersModuleName = node.externalHelpersModuleName; return updateNode(updated, node); } @@ -1530,6 +1529,19 @@ namespace ts { return node; } + /** + * Creates a node that emits a string of raw text in an expression position. Raw text is never + * transformed, should be ES3 compliant, and should have the same precedence as + * PrimaryExpression. + * + * @param text The raw text of the node. + */ + export function createRawExpression(text: string) { + const node = createNode(SyntaxKind.RawExpression); + node.text = text; + return node; + } + // Compound nodes export function createComma(left: Expression, right: Expression) { @@ -1742,278 +1754,8 @@ namespace ts { // Helpers - export function createHelperName(externalHelpersModuleName: Identifier | undefined, name: string) { - return externalHelpersModuleName - ? createPropertyAccess(externalHelpersModuleName, name) - : createIdentifier(name); - } - - export function createExtendsHelper(externalHelpersModuleName: Identifier | undefined, name: Identifier) { - return createCall( - createHelperName(externalHelpersModuleName, "__extends"), - /*typeArguments*/ undefined, - [ - name, - createIdentifier("_super") - ] - ); - } - - export function createAssignHelper(externalHelpersModuleName: Identifier | undefined, attributesSegments: Expression[]) { - return createCall( - createHelperName(externalHelpersModuleName, "__assign"), - /*typeArguments*/ undefined, - attributesSegments - ); - } - - export function createParamHelper(externalHelpersModuleName: Identifier | undefined, expression: Expression, parameterOffset: number, location?: TextRange) { - return createCall( - createHelperName(externalHelpersModuleName, "__param"), - /*typeArguments*/ undefined, - [ - createLiteral(parameterOffset), - expression - ], - location - ); - } - - export function createMetadataHelper(externalHelpersModuleName: Identifier | undefined, metadataKey: string, metadataValue: Expression) { - return createCall( - createHelperName(externalHelpersModuleName, "__metadata"), - /*typeArguments*/ undefined, - [ - createLiteral(metadataKey), - metadataValue - ] - ); - } - - export function createDecorateHelper(externalHelpersModuleName: Identifier | undefined, decoratorExpressions: Expression[], target: Expression, memberName?: Expression, descriptor?: Expression, location?: TextRange) { - const argumentsArray: Expression[] = []; - argumentsArray.push(createArrayLiteral(decoratorExpressions, /*location*/ undefined, /*multiLine*/ true)); - argumentsArray.push(target); - if (memberName) { - argumentsArray.push(memberName); - if (descriptor) { - argumentsArray.push(descriptor); - } - } - - return createCall(createHelperName(externalHelpersModuleName, "__decorate"), /*typeArguments*/ undefined, argumentsArray, location); - } - - export function createAwaiterHelper(externalHelpersModuleName: Identifier | undefined, hasLexicalArguments: boolean, promiseConstructor: EntityName | Expression, body: Block) { - const generatorFunc = createFunctionExpression( - /*modifiers*/ undefined, - createToken(SyntaxKind.AsteriskToken), - /*name*/ undefined, - /*typeParameters*/ undefined, - /*parameters*/ [], - /*type*/ undefined, - body - ); - - // Mark this node as originally an async function - (generatorFunc.emitNode || (generatorFunc.emitNode = {})).flags |= EmitFlags.AsyncFunctionBody; - - return createCall( - createHelperName(externalHelpersModuleName, "__awaiter"), - /*typeArguments*/ undefined, - [ - createThis(), - hasLexicalArguments ? createIdentifier("arguments") : createVoidZero(), - promiseConstructor ? createExpressionFromEntityName(promiseConstructor) : createVoidZero(), - generatorFunc - ] - ); - } - - export function createHasOwnProperty(target: LeftHandSideExpression, propertyName: Expression) { - return createCall( - createPropertyAccess(target, "hasOwnProperty"), - /*typeArguments*/ undefined, - [propertyName] - ); - } - - function createObjectCreate(prototype: Expression) { - return createCall( - createPropertyAccess(createIdentifier("Object"), "create"), - /*typeArguments*/ undefined, - [prototype] - ); - } - - function createGeti(target: LeftHandSideExpression) { - // name => super[name] - return createArrowFunction( - /*modifiers*/ undefined, - /*typeParameters*/ undefined, - [createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, "name")], - /*type*/ undefined, - createToken(SyntaxKind.EqualsGreaterThanToken), - createElementAccess(target, createIdentifier("name")) - ); - } - - function createSeti(target: LeftHandSideExpression) { - // (name, value) => super[name] = value - return createArrowFunction( - /*modifiers*/ undefined, - /*typeParameters*/ undefined, - [ - createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, "name"), - createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, "value") - ], - /*type*/ undefined, - createToken(SyntaxKind.EqualsGreaterThanToken), - createAssignment( - createElementAccess( - target, - createIdentifier("name") - ), - createIdentifier("value") - ) - ); - } - - export function createAdvancedAsyncSuperHelper() { - // const _super = (function (geti, seti) { - // const cache = Object.create(null); - // return name => cache[name] || (cache[name] = { get value() { return geti(name); }, set value(v) { seti(name, v); } }); - // })(name => super[name], (name, value) => super[name] = value); - - // const cache = Object.create(null); - const createCache = createVariableStatement( - /*modifiers*/ undefined, - createConstDeclarationList([ - createVariableDeclaration( - "cache", - /*type*/ undefined, - createObjectCreate(createNull()) - ) - ]) - ); - - // get value() { return geti(name); } - const getter = createGetAccessor( - /*decorators*/ undefined, - /*modifiers*/ undefined, - "value", - /*parameters*/ [], - /*type*/ undefined, - createBlock([ - createReturn( - createCall( - createIdentifier("geti"), - /*typeArguments*/ undefined, - [createIdentifier("name")] - ) - ) - ]) - ); - - // set value(v) { seti(name, v); } - const setter = createSetAccessor( - /*decorators*/ undefined, - /*modifiers*/ undefined, - "value", - [createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, "v")], - createBlock([ - createStatement( - createCall( - createIdentifier("seti"), - /*typeArguments*/ undefined, - [ - createIdentifier("name"), - createIdentifier("v") - ] - ) - ) - ]) - ); - - // return name => cache[name] || ... - const getOrCreateAccessorsForName = createReturn( - createArrowFunction( - /*modifiers*/ undefined, - /*typeParameters*/ undefined, - [createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, "name")], - /*type*/ undefined, - createToken(SyntaxKind.EqualsGreaterThanToken), - createLogicalOr( - createElementAccess( - createIdentifier("cache"), - createIdentifier("name") - ), - createParen( - createAssignment( - createElementAccess( - createIdentifier("cache"), - createIdentifier("name") - ), - createObjectLiteral([ - getter, - setter - ]) - ) - ) - ) - ) - ); - - // const _super = (function (geti, seti) { - // const cache = Object.create(null); - // return name => cache[name] || (cache[name] = { get value() { return geti(name); }, set value(v) { seti(name, v); } }); - // })(name => super[name], (name, value) => super[name] = value); - return createVariableStatement( - /*modifiers*/ undefined, - createConstDeclarationList([ - createVariableDeclaration( - "_super", - /*type*/ undefined, - createCall( - createParen( - createFunctionExpression( - /*modifiers*/ undefined, - /*asteriskToken*/ undefined, - /*name*/ undefined, - /*typeParameters*/ undefined, - [ - createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, "geti"), - createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, "seti") - ], - /*type*/ undefined, - createBlock([ - createCache, - getOrCreateAccessorsForName - ]) - ) - ), - /*typeArguments*/ undefined, - [ - createGeti(createSuper()), - createSeti(createSuper()) - ] - ) - ) - ]) - ); - } - - export function createSimpleAsyncSuperHelper() { - return createVariableStatement( - /*modifiers*/ undefined, - createConstDeclarationList([ - createVariableDeclaration( - "_super", - /*type*/ undefined, - createGeti(createSuper()) - ) - ]) - ); + export function getHelperName(name: string) { + return setEmitFlags(createIdentifier(name), EmitFlags.HelperName | EmitFlags.AdviseOnEmitNode); } // Utilities @@ -2364,10 +2106,7 @@ namespace ts { } export function convertToFunctionBody(node: ConciseBody, multiLine?: boolean) { - if (isBlock(node)) { - return node; - } - return createBlock([createReturn(node, node)], node, multiLine); + return isBlock(node) ? node : createBlock([createReturn(node, /*location*/ node)], /*location*/ node, multiLine); } function isUseStrictPrologue(node: ExpressionStatement): boolean { @@ -2419,14 +2158,21 @@ namespace ts { return statementOffset; } + export function startsWithUseStrict(statements: Statement[]) { + const firstStatement = firstOrUndefined(statements); + return firstStatement !== undefined + && isPrologueDirective(firstStatement) + && isUseStrictPrologue(firstStatement); + } + /** * Ensures "use strict" directive is added * - * @param node source file + * @param statements An array of statements */ - export function ensureUseStrict(node: SourceFile): SourceFile { + export function ensureUseStrict(statements: NodeArray): NodeArray { let foundUseStrict = false; - for (const statement of node.statements) { + for (const statement of statements) { if (isPrologueDirective(statement)) { if (isUseStrictPrologue(statement as ExpressionStatement)) { foundUseStrict = true; @@ -2437,13 +2183,15 @@ namespace ts { break; } } + if (!foundUseStrict) { - const statements: Statement[] = []; - statements.push(startOnNewLine(createStatement(createLiteral("use strict")))); - // add "use strict" as the first statement - return updateSourceFileNode(node, statements.concat(node.statements)); + return createNodeArray([ + startOnNewLine(createStatement(createLiteral("use strict"))), + ...statements + ], statements); } - return node; + + return statements; } /** @@ -2872,12 +2620,21 @@ namespace ts { } function mergeEmitNode(sourceEmitNode: EmitNode, destEmitNode: EmitNode) { - const { flags, commentRange, sourceMapRange, tokenSourceMapRanges } = sourceEmitNode; - if (!destEmitNode && (flags || commentRange || sourceMapRange || tokenSourceMapRanges)) destEmitNode = {}; + const { + flags, + commentRange, + sourceMapRange, + tokenSourceMapRanges, + constantValue, + helpers + } = sourceEmitNode; + if (!destEmitNode) destEmitNode = {}; if (flags) destEmitNode.flags = flags; if (commentRange) destEmitNode.commentRange = commentRange; if (sourceMapRange) destEmitNode.sourceMapRange = sourceMapRange; if (tokenSourceMapRanges) destEmitNode.tokenSourceMapRanges = mergeTokenSourceMapRanges(tokenSourceMapRanges, destEmitNode.tokenSourceMapRanges); + if (constantValue !== undefined) destEmitNode.constantValue = constantValue; + if (helpers) destEmitNode.helpers = addRange(destEmitNode.helpers, helpers); return destEmitNode; } @@ -2914,7 +2671,7 @@ namespace ts { * * @param node The node. */ - function getOrCreateEmitNode(node: Node) { + export function getOrCreateEmitNode(node: Node) { if (!node.emitNode) { if (isParseTreeNode(node)) { // To avoid holding onto transformation artifacts, we keep track of any @@ -2955,6 +2712,16 @@ namespace ts { return node; } + /** + * Gets a custom text range to use when emitting source maps. + * + * @param node The node. + */ + export function getSourceMapRange(node: Node) { + const emitNode = node.emitNode; + return (emitNode && emitNode.sourceMapRange) || node; + } + /** * Sets a custom text range to use when emitting source maps. * @@ -2966,6 +2733,18 @@ namespace ts { return node; } + /** + * Gets the TextRange to use for source maps for a token of a node. + * + * @param node The node. + * @param token The token. + */ + export function getTokenSourceMapRange(node: Node, token: SyntaxKind) { + const emitNode = node.emitNode; + const tokenSourceMapRanges = emitNode && emitNode.tokenSourceMapRanges; + return tokenSourceMapRanges && tokenSourceMapRanges[token]; + } + /** * Sets the TextRange to use for source maps for a token of a node. * @@ -2980,14 +2759,6 @@ namespace ts { return node; } - /** - * Sets a custom text range to use when emitting comments. - */ - export function setCommentRange(node: T, range: TextRange) { - getOrCreateEmitNode(node).commentRange = range; - return node; - } - /** * Gets a custom text range to use when emitting comments. * @@ -2999,25 +2770,11 @@ namespace ts { } /** - * Gets a custom text range to use when emitting source maps. - * - * @param node The node. - */ - export function getSourceMapRange(node: Node) { - const emitNode = node.emitNode; - return (emitNode && emitNode.sourceMapRange) || node; - } - - /** - * Gets the TextRange to use for source maps for a token of a node. - * - * @param node The node. - * @param token The token. + * Sets a custom text range to use when emitting comments. */ - export function getTokenSourceMapRange(node: Node, token: SyntaxKind) { - const emitNode = node.emitNode; - const tokenSourceMapRanges = emitNode && emitNode.tokenSourceMapRanges; - return tokenSourceMapRanges && tokenSourceMapRanges[token]; + export function setCommentRange(node: T, range: TextRange) { + getOrCreateEmitNode(node).commentRange = range; + return node; } /** @@ -3037,6 +2794,113 @@ namespace ts { return node; } + export function getExternalHelpersModuleName(node: SourceFile) { + const parseNode = getOriginalNode(node, isSourceFile); + const emitNode = parseNode && parseNode.emitNode; + return emitNode && emitNode.externalHelpersModuleName; + } + + export function getOrCreateExternalHelpersModuleNameIfNeeded(node: SourceFile, compilerOptions: CompilerOptions) { + if (compilerOptions.importHelpers && (isExternalModule(node) || compilerOptions.isolatedModules)) { + const externalHelpersModuleName = getExternalHelpersModuleName(node); + if (externalHelpersModuleName) { + return externalHelpersModuleName; + } + + const helpers = getEmitHelpers(node); + if (helpers) { + for (const helper of helpers) { + if (!helper.scoped) { + const parseNode = getOriginalNode(node, isSourceFile); + const emitNode = getOrCreateEmitNode(parseNode); + return emitNode.externalHelpersModuleName || (emitNode.externalHelpersModuleName = createUniqueName(externalHelpersModuleNameText)); + } + } + } + } + } + /** + * Adds an EmitHelper to a node. + */ + export function addEmitHelper(node: T, helper: EmitHelper): T { + const emitNode = getOrCreateEmitNode(node); + emitNode.helpers = append(emitNode.helpers, helper); + return node; + } + + /** + * Adds an EmitHelper to a node. + */ + export function addEmitHelpers(node: T, helpers: EmitHelper[] | undefined): T { + if (some(helpers)) { + const emitNode = getOrCreateEmitNode(node); + for (const helper of helpers) { + if (!contains(emitNode.helpers, helper)) { + emitNode.helpers = append(emitNode.helpers, helper); + } + } + } + return node; + } + + /** + * Removes an EmitHelper from a node. + */ + export function removeEmitHelper(node: Node, helper: EmitHelper): boolean { + const emitNode = node.emitNode; + if (emitNode) { + const helpers = emitNode.helpers; + if (helpers) { + return orderedRemoveItem(helpers, helper); + } + } + return false; + } + + /** + * Gets the EmitHelpers of a node. + */ + export function getEmitHelpers(node: Node): EmitHelper[] | undefined { + const emitNode = node.emitNode; + return emitNode && emitNode.helpers; + } + + /** + * Moves matching emit helpers from a source node to a target node. + */ + export function moveEmitHelpers(source: Node, target: Node, predicate: (helper: EmitHelper) => boolean) { + const sourceEmitNode = source.emitNode; + const sourceEmitHelpers = sourceEmitNode && sourceEmitNode.helpers; + if (!some(sourceEmitHelpers)) return; + + const targetEmitNode = getOrCreateEmitNode(target); + let helpersRemoved = 0; + for (let i = 0; i < sourceEmitHelpers.length; i++) { + const helper = sourceEmitHelpers[i]; + if (predicate(helper)) { + helpersRemoved++; + if (!contains(targetEmitNode.helpers, helper)) { + targetEmitNode.helpers = append(targetEmitNode.helpers, helper); + } + } + else if (helpersRemoved > 0) { + sourceEmitHelpers[i - helpersRemoved] = helper; + } + } + + if (helpersRemoved > 0) { + sourceEmitHelpers.length -= helpersRemoved; + } + } + + export function compareEmitHelpers(x: EmitHelper, y: EmitHelper) { + if (x === y) return Comparison.EqualTo; + if (x.priority === y.priority) return Comparison.EqualTo; + if (x.priority === undefined) return Comparison.GreaterThan; + if (y.priority === undefined) return Comparison.LessThan; + return compareValues(x.priority, y.priority); + } + export function setTextRange(node: T, location: TextRange): T { if (location) { node.pos = location.pos; @@ -3400,4 +3264,165 @@ namespace ts { Debug.assertNode(node, isExpression); return node; } + + export interface ExternalModuleInfo { + externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[]; // imports of other external modules + externalHelpersImportDeclaration: ImportDeclaration | undefined; // import of external helpers + exportSpecifiers: Map; // export specifiers by name + exportedBindings: Map; // exported names of local declarations + exportedNames: Identifier[]; // all exported names local to module + exportEquals: ExportAssignment | undefined; // an export= declaration if one was present + hasExportStarsToExportValues: boolean; // whether this module contains export* + } + + export function collectExternalModuleInfo(sourceFile: SourceFile, resolver: EmitResolver, compilerOptions: CompilerOptions): ExternalModuleInfo { + const externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[] = []; + const exportSpecifiers = createMap(); + const exportedBindings = createMap(); + const uniqueExports = createMap(); + let exportedNames: Identifier[]; + let hasExportDefault = false; + let exportEquals: ExportAssignment = undefined; + let hasExportStarsToExportValues = false; + + const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(sourceFile, compilerOptions); + const externalHelpersImportDeclaration = externalHelpersModuleName && createImportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + createImportClause(/*name*/ undefined, createNamespaceImport(externalHelpersModuleName)), + createLiteral(externalHelpersModuleNameText)); + + if (externalHelpersImportDeclaration) { + externalImports.push(externalHelpersImportDeclaration); + } + + for (const node of sourceFile.statements) { + switch (node.kind) { + case SyntaxKind.ImportDeclaration: + // import "mod" + // import x from "mod" + // import * as x from "mod" + // import { x, y } from "mod" + externalImports.push(node); + break; + + case SyntaxKind.ImportEqualsDeclaration: + if ((node).moduleReference.kind === SyntaxKind.ExternalModuleReference) { + // import x = require("mod") + externalImports.push(node); + } + + break; + + case SyntaxKind.ExportDeclaration: + if ((node).moduleSpecifier) { + if (!(node).exportClause) { + // export * from "mod" + externalImports.push(node); + hasExportStarsToExportValues = true; + } + else { + // export { x, y } from "mod" + externalImports.push(node); + } + } + else { + // export { x, y } + for (const specifier of (node).exportClause.elements) { + if (!uniqueExports[specifier.name.text]) { + const name = specifier.propertyName || specifier.name; + multiMapAdd(exportSpecifiers, name.text, specifier); + + const decl = resolver.getReferencedImportDeclaration(name) + || resolver.getReferencedValueDeclaration(name); + + if (decl) { + multiMapAdd(exportedBindings, getOriginalNodeId(decl), specifier.name); + } + + uniqueExports[specifier.name.text] = true; + exportedNames = append(exportedNames, specifier.name); + } + } + } + break; + + case SyntaxKind.ExportAssignment: + if ((node).isExportEquals && !exportEquals) { + // export = x + exportEquals = node; + } + break; + + case SyntaxKind.VariableStatement: + if (hasModifier(node, ModifierFlags.Export)) { + for (const decl of (node).declarationList.declarations) { + exportedNames = collectExportedVariableInfo(decl, uniqueExports, exportedNames); + } + } + break; + + case SyntaxKind.FunctionDeclaration: + if (hasModifier(node, ModifierFlags.Export)) { + if (hasModifier(node, ModifierFlags.Default)) { + // export default function() { } + if (!hasExportDefault) { + multiMapAdd(exportedBindings, getOriginalNodeId(node), getDeclarationName(node)); + hasExportDefault = true; + } + } + else { + // export function x() { } + const name = (node).name; + if (!uniqueExports[name.text]) { + multiMapAdd(exportedBindings, getOriginalNodeId(node), name); + uniqueExports[name.text] = true; + exportedNames = append(exportedNames, name); + } + } + } + break; + + case SyntaxKind.ClassDeclaration: + if (hasModifier(node, ModifierFlags.Export)) { + if (hasModifier(node, ModifierFlags.Default)) { + // export default class { } + if (!hasExportDefault) { + multiMapAdd(exportedBindings, getOriginalNodeId(node), getDeclarationName(node)); + hasExportDefault = true; + } + } + else { + // export class x { } + const name = (node).name; + if (!uniqueExports[name.text]) { + multiMapAdd(exportedBindings, getOriginalNodeId(node), name); + uniqueExports[name.text] = true; + exportedNames = append(exportedNames, name); + } + } + } + break; + } + } + + return { externalImports, exportSpecifiers, exportEquals, hasExportStarsToExportValues, exportedBindings, exportedNames, externalHelpersImportDeclaration }; + } + + function collectExportedVariableInfo(decl: VariableDeclaration | BindingElement, uniqueExports: Map, exportedNames: Identifier[]) { + if (isBindingPattern(decl.name)) { + for (const element of decl.name.elements) { + if (!isOmittedExpression(element)) { + exportedNames = collectExportedVariableInfo(element, uniqueExports, exportedNames); + } + } + } + else if (!isGeneratedIdentifier(decl.name)) { + if (!uniqueExports[decl.name.text]) { + uniqueExports[decl.name.text] = true; + exportedNames = append(exportedNames, decl.name); + } + } + return exportedNames; + } } diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index fbf8eb9e76293..10a718448e9e7 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -27,84 +27,6 @@ namespace ts { EmitNotifications = 1 << 1, } - export interface TransformationResult { - /** - * Gets the transformed source files. - */ - transformed: SourceFile[]; - - /** - * Emits the substitute for a node, if one is available; otherwise, emits the node. - * - * @param emitContext The current emit context. - * @param node The node to substitute. - * @param emitCallback A callback used to emit the node or its substitute. - */ - emitNodeWithSubstitution(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void; - - /** - * Emits a node with possible notification. - * - * @param emitContext The current emit context. - * @param node The node to emit. - * @param emitCallback A callback used to emit the node. - */ - emitNodeWithNotification(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void; - } - - export interface TransformationContext extends LexicalEnvironment { - getCompilerOptions(): CompilerOptions; - getEmitResolver(): EmitResolver; - getEmitHost(): EmitHost; - - /** - * Hoists a function declaration to the containing scope. - */ - hoistFunctionDeclaration(node: FunctionDeclaration): void; - - /** - * Hoists a variable declaration to the containing scope. - */ - hoistVariableDeclaration(node: Identifier): void; - - /** - * Enables expression substitutions in the pretty printer for the provided SyntaxKind. - */ - enableSubstitution(kind: SyntaxKind): void; - - /** - * Determines whether expression substitutions are enabled for the provided node. - */ - isSubstitutionEnabled(node: Node): boolean; - - /** - * Hook used by transformers to substitute expressions just before they - * are emitted by the pretty printer. - */ - onSubstituteNode?: (emitContext: EmitContext, node: Node) => Node; - - /** - * Enables before/after emit notifications in the pretty printer for the provided - * SyntaxKind. - */ - enableEmitNotification(kind: SyntaxKind): void; - - /** - * Determines whether before/after emit notifications should be raised in the pretty - * printer when it emits a node. - */ - isEmitNotificationEnabled(node: Node): boolean; - - /** - * Hook used to allow transformers to capture state before or after - * the printer emits a node. - */ - onEmitNode?: (emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void) => void; - } - - /* @internal */ - export type Transformer = (context: TransformationContext) => (node: SourceFile) => SourceFile; - export function getTransformers(compilerOptions: CompilerOptions) { const jsx = compilerOptions.jsx; const languageVersion = getEmitScriptTarget(compilerOptions); @@ -158,13 +80,15 @@ namespace ts { let lexicalEnvironmentDisabled = false; - let lexicalEnvironmentStackOffset = 0; let lexicalEnvironmentVariableDeclarations: VariableDeclaration[]; let lexicalEnvironmentFunctionDeclarations: FunctionDeclaration[]; let lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = []; let lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = []; + let lexicalEnvironmentStackOffset = 0; let lexicalEnvironmentSuspended = false; + let emitHelpers: EmitHelper[]; + // The transformation context is provided to each transformer as part of transformer // initialization. const context: TransformationContext = { @@ -177,6 +101,8 @@ namespace ts { endLexicalEnvironment, hoistVariableDeclaration, hoistFunctionDeclaration, + requestEmitHelper, + readEmitHelpers, onSubstituteNode: (_emitContext, node) => node, enableSubstitution, isSubstitutionEnabled, @@ -339,7 +265,7 @@ namespace ts { /** Resumes a suspended lexical environment, usually before visiting a function body. */ function resumeLexicalEnvironment(): void { Debug.assert(!lexicalEnvironmentDisabled, "Cannot resume a lexical environment during the print phase."); - Debug.assert(lexicalEnvironmentSuspended, "Lexical environment is not suspended suspended."); + Debug.assert(lexicalEnvironmentSuspended, "Lexical environment is not suspended."); lexicalEnvironmentSuspended = false; } @@ -382,5 +308,18 @@ namespace ts { } return statements; } + + function requestEmitHelper(helper: EmitHelper): void { + Debug.assert(!lexicalEnvironmentDisabled, "Cannot modify the lexical environment during the print phase."); + Debug.assert(!helper.scoped, "Cannot request a scoped emit helper."); + emitHelpers = append(emitHelpers, helper); + } + + function readEmitHelpers(): EmitHelper[] | undefined { + Debug.assert(!lexicalEnvironmentDisabled, "Cannot modify the lexical environment during the print phase."); + const helpers = emitHelpers; + emitHelpers = undefined; + return helpers; + } } } diff --git a/src/compiler/transformers/destructuring.ts b/src/compiler/transformers/destructuring.ts index 3e63542ab7e21..43590791c83c6 100644 --- a/src/compiler/transformers/destructuring.ts +++ b/src/compiler/transformers/destructuring.ts @@ -287,7 +287,7 @@ namespace ts { flattenContext.emitBindingOrAssignment(flattenContext.createObjectBindingOrAssignmentPattern(bindingElements), value, location, pattern); bindingElements = undefined; } - const rhsValue = createRestCall(value, elements, computedTempVariables, pattern); + const rhsValue = createRestCall(flattenContext.context, value, elements, computedTempVariables, pattern); flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, element); } } @@ -451,9 +451,25 @@ namespace ts { return name; } + const restHelper: EmitHelper = { + name: "typescript:rest", + scoped: false, + text: ` + var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0) + t[p[i]] = s[p[i]]; + return t; + };` + }; + /** Given value: o, propName: p, pattern: { a, b, ...p } from the original statement * `{ a, b, ...p } = o`, create `p = __rest(o, ["a", "b"]);`*/ - function createRestCall(value: Expression, elements: BindingOrAssignmentElement[], computedTempVariables: Expression[], location: TextRange): Expression { + function createRestCall(context: TransformationContext, value: Expression, elements: BindingOrAssignmentElement[], computedTempVariables: Expression[], location: TextRange): Expression { + context.requestEmitHelper(restHelper); const propertyNames: Expression[] = []; let computedTempVariableOffset = 0; for (let i = 0; i < elements.length - 1; i++) { @@ -476,6 +492,6 @@ namespace ts { } } } - return createCall(createIdentifier("__rest"), undefined, [value, createArrayLiteral(propertyNames, location)]); + return createCall(getHelperName("__rest"), undefined, [value, createArrayLiteral(propertyNames, location)]); } } diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index ca83b40dc6815..838e58ae99b5c 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -3,7 +3,6 @@ /*@internal*/ namespace ts { - const enum ES2015SubstitutionFlags { /** Enables substitutions for captured `this` */ CapturedThis = 1 << 0, @@ -210,7 +209,13 @@ namespace ts { currentSourceFile = node; currentText = node.text; - return visitNode(node, visitor, isSourceFile); + + const visited = saveStateAndInvoke(node, visitSourceFile); + addEmitHelpers(visited, context.readEmitHelpers()); + + currentSourceFile = undefined; + currentText = undefined; + return visited; } function visitor(node: Node): VisitResult { @@ -256,6 +261,47 @@ namespace ts { return visited; } + function onBeforeVisitNode(node: Node) { + if (currentNode) { + if (isBlockScope(currentNode, currentParent)) { + enclosingBlockScopeContainer = currentNode; + enclosingBlockScopeContainerParent = currentParent; + } + + if (isFunctionLike(currentNode)) { + enclosingFunction = currentNode; + if (currentNode.kind !== SyntaxKind.ArrowFunction) { + enclosingNonArrowFunction = currentNode; + if (!(getEmitFlags(currentNode) & EmitFlags.AsyncFunctionBody)) { + enclosingNonAsyncFunctionBody = currentNode; + } + } + } + + // keep track of the enclosing variable statement when in the context of + // variable statements, variable declarations, binding elements, and binding + // patterns. + switch (currentNode.kind) { + case SyntaxKind.VariableStatement: + enclosingVariableStatement = currentNode; + break; + + case SyntaxKind.VariableDeclarationList: + case SyntaxKind.VariableDeclaration: + case SyntaxKind.BindingElement: + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + break; + + default: + enclosingVariableStatement = undefined; + } + } + + currentParent = currentNode; + currentNode = node; + } + function returnCapturedThis(node: Node): Node { return setOriginalNode(createReturn(createIdentifier("_this")), node); } @@ -418,6 +464,9 @@ namespace ts { case SyntaxKind.YieldExpression: return visitYieldExpression(node); + case SyntaxKind.SpreadElement: + return visitSpreadElement(node); + case SyntaxKind.SuperKeyword: return visitSuperKeyword(); @@ -428,9 +477,6 @@ namespace ts { case SyntaxKind.MethodDeclaration: return visitMethodDeclaration(node); - case SyntaxKind.SourceFile: - return visitSourceFileNode(node); - case SyntaxKind.VariableStatement: return visitVariableStatement(node); @@ -438,48 +484,19 @@ namespace ts { Debug.failBadSyntaxKind(node); return visitEachChild(node, visitor, context); } - } - function onBeforeVisitNode(node: Node) { - if (currentNode) { - if (isBlockScope(currentNode, currentParent)) { - enclosingBlockScopeContainer = currentNode; - enclosingBlockScopeContainerParent = currentParent; - } - - if (isFunctionLike(currentNode)) { - enclosingFunction = currentNode; - if (currentNode.kind !== SyntaxKind.ArrowFunction) { - enclosingNonArrowFunction = currentNode; - if (!(getEmitFlags(currentNode) & EmitFlags.AsyncFunctionBody)) { - enclosingNonAsyncFunctionBody = currentNode; - } - } - } - - // keep track of the enclosing variable statement when in the context of - // variable statements, variable declarations, binding elements, and binding - // patterns. - switch (currentNode.kind) { - case SyntaxKind.VariableStatement: - enclosingVariableStatement = currentNode; - break; - - case SyntaxKind.VariableDeclarationList: - case SyntaxKind.VariableDeclaration: - case SyntaxKind.BindingElement: - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.ArrayBindingPattern: - break; - - default: - enclosingVariableStatement = undefined; - } - } - - currentParent = currentNode; - currentNode = node; + function visitSourceFile(node: SourceFile): SourceFile { + const statements: Statement[] = []; + startLexicalEnvironment(); + const statementOffset = addPrologueDirectives(statements, node.statements, /*ensureUseStrict*/ false, visitor); + addCaptureThisForNodeIfNeeded(statements, node); + addRange(statements, visitNodes(node.statements, visitor, isStatement, statementOffset)); + addRange(statements, endLexicalEnvironment()); + return updateSourceFileNode( + node, + createNodeArray(statements, node.statements) + ); } function visitSwitchStatement(node: SwitchStatement): SwitchStatement { @@ -779,7 +796,7 @@ namespace ts { if (extendsClauseElement) { statements.push( createStatement( - createExtendsHelper(currentSourceFile.externalHelpersModuleName, getLocalName(node)), + createExtendsHelper(context, getLocalName(node)), /*location*/ extendsClauseElement ) ); @@ -851,7 +868,7 @@ namespace ts { // If a super call has already been synthesized, // we're going to assume that we should just transform everything after that. // The assumption is that no prior step in the pipeline has added any prologue directives. - statementOffset = 1; + statementOffset = 0; } else if (constructor) { // Otherwise, try to emit all potential prologue directives first. @@ -1391,20 +1408,13 @@ namespace ts { function transformClassMethodDeclarationToStatement(receiver: LeftHandSideExpression, member: MethodDeclaration) { const commentRange = getCommentRange(member); const sourceMapRange = getSourceMapRange(member); - - const func = transformFunctionLikeToExpression(member, /*location*/ member, /*name*/ undefined); - setEmitFlags(func, EmitFlags.NoComments); - setSourceMapRange(func, sourceMapRange); + const memberName = createMemberAccessForPropertyName(receiver, visitNode(member.name, visitor, isPropertyName), /*location*/ member.name); + const memberFunction = transformFunctionLikeToExpression(member, /*location*/ member, /*name*/ undefined); + setEmitFlags(memberFunction, EmitFlags.NoComments); + setSourceMapRange(memberFunction, sourceMapRange); const statement = createStatement( - createAssignment( - createMemberAccessForPropertyName( - receiver, - visitNode(member.name, visitor, isPropertyName), - /*location*/ member.name - ), - func - ), + createAssignment(memberName, memberFunction), /*location*/ member ); @@ -1682,7 +1692,7 @@ namespace ts { * * @param node An ExpressionStatement node. */ - function visitExpressionStatement(node: ExpressionStatement): ExpressionStatement { + function visitExpressionStatement(node: ExpressionStatement): Statement { // If we are here it is most likely because our expression is a destructuring assignment. switch (node.expression.kind) { case SyntaxKind.ParenthesizedExpression: @@ -1722,13 +1732,14 @@ namespace ts { */ function visitBinaryExpression(node: BinaryExpression, needsDestructuringValue: boolean): Expression { // If we are here it is because this is a destructuring assignment. - Debug.assert(isDestructuringAssignment(node)); - return flattenDestructuringAssignment( - node, - visitor, - context, - FlattenLevel.All, - needsDestructuringValue); + if (isDestructuringAssignment(node)) { + return flattenDestructuringAssignment( + node, + visitor, + context, + FlattenLevel.All, + needsDestructuringValue); + } } function visitVariableStatement(node: VariableStatement): Statement { @@ -2927,6 +2938,10 @@ namespace ts { ); } + function visitSpreadElement(node: SpreadElement) { + return visitNode(node.expression, visitor, isExpression); + } + /** * Transforms the expression of a SpreadExpression node. * @@ -3113,19 +3128,6 @@ namespace ts { : createIdentifier("_super"); } - function visitSourceFileNode(node: SourceFile): SourceFile { - const [prologue, remaining] = span(node.statements, isPrologueDirective); - const statements: Statement[] = []; - startLexicalEnvironment(); - addRange(statements, prologue); - addCaptureThisForNodeIfNeeded(statements, node); - addRange(statements, visitNodes(createNodeArray(remaining), visitor, isStatement)); - addRange(statements, endLexicalEnvironment()); - const clone = getMutableClone(node); - clone.statements = createNodeArray(statements, /*location*/ node.statements); - return clone; - } - /** * Called by the printer just before a node is printed. * @@ -3287,8 +3289,7 @@ namespace ts { return false; } - const parameter = singleOrUndefined(constructor.parameters); - if (!parameter || !nodeIsSynthesized(parameter) || !parameter.dotDotDotToken) { + if (some(constructor.parameters)) { return false; } @@ -3313,7 +3314,31 @@ namespace ts { } const expression = (callArgument).expression; - return isIdentifier(expression) && expression === parameter.name; + return isIdentifier(expression) && expression.text === "arguments"; } } + + function createExtendsHelper(context: TransformationContext, name: Identifier) { + context.requestEmitHelper(extendsHelper); + return createCall( + getHelperName("__extends"), + /*typeArguments*/ undefined, + [ + name, + createIdentifier("_super") + ] + ); + } + + const extendsHelper: EmitHelper = { + name: "typescript:extends", + scoped: false, + priority: 0, + text: ` + var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + };` + }; } diff --git a/src/compiler/transformers/es2017.ts b/src/compiler/transformers/es2017.ts index 35de10a5a5948..14f840f2e6659 100644 --- a/src/compiler/transformers/es2017.ts +++ b/src/compiler/transformers/es2017.ts @@ -5,13 +5,12 @@ namespace ts { type SuperContainer = ClassDeclaration | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | ConstructorDeclaration; - export function transformES2017(context: TransformationContext) { - - const enum ES2017SubstitutionFlags { - /** Enables substitutions for async methods with `super` calls. */ - AsyncMethodsWithSuper = 1 << 0 - } + const enum ES2017SubstitutionFlags { + /** Enables substitutions for async methods with `super` calls. */ + AsyncMethodsWithSuper = 1 << 0 + } + export function transformES2017(context: TransformationContext) { const { startLexicalEnvironment, resumeLexicalEnvironment, @@ -23,7 +22,8 @@ namespace ts { const languageVersion = getEmitScriptTarget(compilerOptions); // These variables contain state that changes as we descend into the tree. - let currentSourceFileExternalHelpersModuleName: Identifier; + let currentSourceFile: SourceFile; + /** * Keeps track of whether expression substitution has been enabled for specific edge cases. * They are persisted between each SourceFile transformation and should not be reset. @@ -51,8 +51,13 @@ namespace ts { return node; } - currentSourceFileExternalHelpersModuleName = node.externalHelpersModuleName; - return visitEachChild(node, visitor, context); + currentSourceFile = node; + + const visited = visitEachChild(node, visitor, context); + addEmitHelpers(visited, context.readEmitHelpers()); + + currentSourceFile = undefined; + return visited; } function visitor(node: Node): VisitResult { @@ -91,11 +96,11 @@ namespace ts { } /** - * Visits an await expression. + * Visits an AwaitExpression node. * * This function will be called any time a ES2017 await expression is encountered. * - * @param node The await expression node. + * @param node The node to visit. */ function visitAwaitExpression(node: AwaitExpression): Expression { return setOriginalNode( @@ -109,12 +114,12 @@ namespace ts { } /** - * Visits a method declaration of a class. + * Visits a MethodDeclaration node. * * This function will be called when one of the following conditions are met: * - The node is marked as async * - * @param node The method node. + * @param node The node to visit. */ function visitMethodDeclaration(node: MethodDeclaration) { return updateMethod( @@ -132,12 +137,12 @@ namespace ts { } /** - * Visits a function declaration. + * Visits a FunctionDeclaration node. * * This function will be called when one of the following conditions are met: * - The node is marked async * - * @param node The function node. + * @param node The node to visit. */ function visitFunctionDeclaration(node: FunctionDeclaration): VisitResult { return updateFunctionDeclaration( @@ -155,12 +160,12 @@ namespace ts { } /** - * Visits a function expression node. + * Visits a FunctionExpression node. * * This function will be called when one of the following conditions are met: * - The node is marked async * - * @param node The function expression node. + * @param node The node to visit. */ function visitFunctionExpression(node: FunctionExpression): Expression { if (nodeIsMissing(node.body)) { @@ -180,9 +185,12 @@ namespace ts { } /** - * @remarks + * Visits an ArrowFunction. + * * This function will be called when one of the following conditions are met: * - The node is marked async + * + * @param node The node to visit. */ function visitArrowFunction(node: ArrowFunction) { return updateArrowFunction( @@ -220,7 +228,7 @@ namespace ts { statements.push( createReturn( createAwaiterHelper( - currentSourceFileExternalHelpersModuleName, + context, hasLexicalArguments, promiseConstructor, transformFunctionBodyWorker(node.body, statementOffset) @@ -229,6 +237,7 @@ namespace ts { ); addRange(statements, endLexicalEnvironment()); + const block = createBlock(statements, /*location*/ node.body, /*multiLine*/ true); // Minor optimization, emit `_super` helper to capture `super` access in an arrow. @@ -236,11 +245,11 @@ namespace ts { if (languageVersion >= ScriptTarget.ES2015) { if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuperBinding) { enableSubstitutionForAsyncMethodsWithSuper(); - setEmitFlags(block, EmitFlags.EmitAdvancedSuperHelper); + addEmitHelper(block, advancedAsyncSuperHelper); } else if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuper) { enableSubstitutionForAsyncMethodsWithSuper(); - setEmitFlags(block, EmitFlags.EmitSuperHelper); + addEmitHelper(block, asyncSuperHelper); } } @@ -248,7 +257,7 @@ namespace ts { } else { const expression = createAwaiterHelper( - currentSourceFileExternalHelpersModuleName, + context, hasLexicalArguments, promiseConstructor, transformFunctionBodyWorker(node.body) @@ -277,16 +286,15 @@ namespace ts { } function getPromiseConstructor(type: TypeNode) { - if (type) { - const typeName = getEntityNameFromTypeNode(type); - if (typeName && isEntityName(typeName)) { - const serializationKind = resolver.getTypeReferenceSerializationKind(typeName); - if (serializationKind === TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue - || serializationKind === TypeReferenceSerializationKind.Unknown) { - return typeName; - } + const typeName = type && getEntityNameFromTypeNode(type); + if (typeName && isEntityName(typeName)) { + const serializationKind = resolver.getTypeReferenceSerializationKind(typeName); + if (serializationKind === TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue + || serializationKind === TypeReferenceSerializationKind.Unknown) { + return typeName; } } + return undefined; } @@ -448,4 +456,63 @@ namespace ts { && resolver.getNodeCheckFlags(currentSuperContainer) & (NodeCheckFlags.AsyncMethodWithSuper | NodeCheckFlags.AsyncMethodWithSuperBinding); } } + + function createAwaiterHelper(context: TransformationContext, hasLexicalArguments: boolean, promiseConstructor: EntityName | Expression, body: Block) { + context.requestEmitHelper(awaiterHelper); + const generatorFunc = createFunctionExpression( + /*modifiers*/ undefined, + createToken(SyntaxKind.AsteriskToken), + /*name*/ undefined, + /*typeParameters*/ undefined, + /*parameters*/ [], + /*type*/ undefined, + body + ); + + // Mark this node as originally an async function + (generatorFunc.emitNode || (generatorFunc.emitNode = {})).flags |= EmitFlags.AsyncFunctionBody; + + return createCall( + getHelperName("__awaiter"), + /*typeArguments*/ undefined, + [ + createThis(), + hasLexicalArguments ? createIdentifier("arguments") : createVoidZero(), + promiseConstructor ? createExpressionFromEntityName(promiseConstructor) : createVoidZero(), + generatorFunc + ] + ); + } + + const awaiterHelper: EmitHelper = { + name: "typescript:awaiter", + scoped: false, + priority: 5, + text: ` + var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments)).next()); + }); + };` + }; + + const asyncSuperHelper: EmitHelper = { + name: "typescript:async-super", + scoped: true, + text: ` + const _super = name => super[name];` + }; + + const advancedAsyncSuperHelper: EmitHelper = { + name: "typescript:advanced-async-super", + scoped: true, + text: ` + const _super = (function (geti, seti) { + const cache = Object.create(null); + return name => cache[name] || (cache[name] = { get value() { return geti(name); }, set value(v) { seti(name, v); } }); + })(name => super[name], (name, value) => super[name] = value);` + }; } diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 933ae88b58abb..ab9806924166d 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -15,7 +15,9 @@ namespace ts { return node; } - return visitEachChild(node, visitor, context); + const visited = visitEachChild(node, visitor, context); + addEmitHelpers(visited, context.readEmitHelpers()); + return visited; } function visitor(node: Node): VisitResult { @@ -112,7 +114,7 @@ namespace ts { if (objects.length && objects[0].kind !== SyntaxKind.ObjectLiteralExpression) { objects.unshift(createObjectLiteral()); } - return createCall(createIdentifier("__assign"), undefined, objects); + return createAssignHelper(context, objects); } return visitEachChild(node, visitor, context); } @@ -383,4 +385,31 @@ namespace ts { return body; } } + + const assignHelper: EmitHelper = { + name: "typescript:assign", + scoped: false, + priority: 1, + text: ` + var __assign = (this && this.__assign) || Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + if (typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) + t[p[i]] = s[p[i]]; + } + return t; + };` + }; + + export function createAssignHelper(context: TransformationContext, attributesSegments: Expression[]) { + context.requestEmitHelper(assignHelper); + return createCall( + getHelperName("__assign"), + /*typeArguments*/ undefined, + attributesSegments + ); + } } diff --git a/src/compiler/transformers/generators.ts b/src/compiler/transformers/generators.ts index 5d2fec82f5eea..c383902d4959e 100644 --- a/src/compiler/transformers/generators.ts +++ b/src/compiler/transformers/generators.ts @@ -291,17 +291,18 @@ namespace ts { return transformSourceFile; function transformSourceFile(node: SourceFile) { - if (isDeclarationFile(node)) { + if (isDeclarationFile(node) + || (node.transformFlags & TransformFlags.ContainsGenerator) === 0) { return node; } - if (node.transformFlags & TransformFlags.ContainsGenerator) { - currentSourceFile = node; - node = visitEachChild(node, visitor, context); - currentSourceFile = undefined; - } + currentSourceFile = node; - return node; + const visited = visitEachChild(node, visitor, context); + addEmitHelpers(visited, context.readEmitHelpers()); + + currentSourceFile = undefined; + return visited; } /** @@ -2597,28 +2598,24 @@ namespace ts { withBlockStack = undefined; const buildResult = buildStatements(); - return createCall( - createHelperName(currentSourceFile.externalHelpersModuleName, "__generator"), - /*typeArguments*/ undefined, - [ - createThis(), - setEmitFlags( - createFunctionExpression( - /*modifiers*/ undefined, - /*asteriskToken*/ undefined, - /*name*/ undefined, - /*typeParameters*/ undefined, - [createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, state)], - /*type*/ undefined, - createBlock( - buildResult, - /*location*/ undefined, - /*multiLine*/ buildResult.length > 0 - ) - ), - EmitFlags.ReuseTempVariableScope - ) - ] + return createGeneratorHelper( + context, + setEmitFlags( + createFunctionExpression( + /*modifiers*/ undefined, + /*asteriskToken*/ undefined, + /*name*/ undefined, + /*typeParameters*/ undefined, + [createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, state)], + /*type*/ undefined, + createBlock( + buildResult, + /*location*/ undefined, + /*multiLine*/ buildResult.length > 0 + ) + ), + EmitFlags.ReuseTempVariableScope + ) ); } @@ -3094,4 +3091,105 @@ namespace ts { ); } } + + function createGeneratorHelper(context: TransformationContext, body: FunctionExpression) { + context.requestEmitHelper(generatorHelper); + return createCall( + getHelperName("__generator"), + /*typeArguments*/ undefined, + [createThis(), body]); + } + + // The __generator helper is used by down-level transformations to emulate the runtime + // semantics of an ES2015 generator function. When called, this helper returns an + // object that implements the Iterator protocol, in that it has `next`, `return`, and + // `throw` methods that step through the generator when invoked. + // + // parameters: + // thisArg The value to use as the `this` binding for the transformed generator body. + // body A function that acts as the transformed generator body. + // + // variables: + // _ Persistent state for the generator that is shared between the helper and the + // generator body. The state object has the following members: + // sent() - A method that returns or throws the current completion value. + // label - The next point at which to resume evaluation of the generator body. + // trys - A stack of protected regions (try/catch/finally blocks). + // ops - A stack of pending instructions when inside of a finally block. + // f A value indicating whether the generator is executing. + // y An iterator to delegate for a yield*. + // t A temporary variable that holds one of the following values (note that these + // cases do not overlap): + // - The completion value when resuming from a `yield` or `yield*`. + // - The error value for a catch block. + // - The current protected region (array of try/catch/finally/end labels). + // - The verb (`next`, `throw`, or `return` method) to delegate to the expression + // of a `yield*`. + // - The result of evaluating the verb delegated to the expression of a `yield*`. + // + // functions: + // verb(n) Creates a bound callback to the `step` function for opcode `n`. + // step(op) Evaluates opcodes in a generator body until execution is suspended or + // completed. + // + // The __generator helper understands a limited set of instructions: + // 0: next(value?) - Start or resume the generator with the specified value. + // 1: throw(error) - Resume the generator with an exception. If the generator is + // suspended inside of one or more protected regions, evaluates + // any intervening finally blocks between the current label and + // the nearest catch block or function boundary. If uncaught, the + // exception is thrown to the caller. + // 2: return(value?) - Resume the generator as if with a return. If the generator is + // suspended inside of one or more protected regions, evaluates any + // intervening finally blocks. + // 3: break(label) - Jump to the specified label. If the label is outside of the + // current protected region, evaluates any intervening finally + // blocks. + // 4: yield(value?) - Yield execution to the caller with an optional value. When + // resumed, the generator will continue at the next label. + // 5: yield*(value) - Delegates evaluation to the supplied iterator. When + // delegation completes, the generator will continue at the next + // label. + // 6: catch(error) - Handles an exception thrown from within the generator body. If + // the current label is inside of one or more protected regions, + // evaluates any intervening finally blocks between the current + // label and the nearest catch block or function boundary. If + // uncaught, the exception is thrown to the caller. + // 7: endfinally - Ends a finally block, resuming the last instruction prior to + // entering a finally block. + // + // For examples of how these are used, see the comments in ./transformers/generators.ts + const generatorHelper: EmitHelper = { + name: "typescript:generator", + scoped: false, + priority: 6, + text: ` + var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t; + return { next: verb(0), "throw": verb(1), "return": verb(2) }; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [0, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } + };` + }; } diff --git a/src/compiler/transformers/jsx.ts b/src/compiler/transformers/jsx.ts index 8bc09ea45b201..ecb5dd053e084 100644 --- a/src/compiler/transformers/jsx.ts +++ b/src/compiler/transformers/jsx.ts @@ -1,13 +1,13 @@ /// /// +/// /*@internal*/ namespace ts { - const entities: Map = createEntitiesMap(); - export function transformJsx(context: TransformationContext) { const compilerOptions = context.getCompilerOptions(); let currentSourceFile: SourceFile; + return transformSourceFile; /** @@ -21,9 +21,12 @@ namespace ts { } currentSourceFile = node; - node = visitEachChild(node, visitor, context); + + const visited = visitEachChild(node, visitor, context); + addEmitHelpers(visited, context.readEmitHelpers()); + currentSourceFile = undefined; - return node; + return visited; } function visitor(node: Node): VisitResult { @@ -105,8 +108,10 @@ namespace ts { // Either emit one big object literal (no spread attribs), or // a call to the __assign helper. - objectProperties = singleOrUndefined(segments) - || createAssignHelper(currentSourceFile.externalHelpersModuleName, segments); + objectProperties = singleOrUndefined(segments); + if (!objectProperties) { + objectProperties = createAssignHelper(context, segments); + } } const element = createExpressionForJsxElement( @@ -272,261 +277,259 @@ namespace ts { } } - function createEntitiesMap(): Map { - return createMap({ - "quot": 0x0022, - "amp": 0x0026, - "apos": 0x0027, - "lt": 0x003C, - "gt": 0x003E, - "nbsp": 0x00A0, - "iexcl": 0x00A1, - "cent": 0x00A2, - "pound": 0x00A3, - "curren": 0x00A4, - "yen": 0x00A5, - "brvbar": 0x00A6, - "sect": 0x00A7, - "uml": 0x00A8, - "copy": 0x00A9, - "ordf": 0x00AA, - "laquo": 0x00AB, - "not": 0x00AC, - "shy": 0x00AD, - "reg": 0x00AE, - "macr": 0x00AF, - "deg": 0x00B0, - "plusmn": 0x00B1, - "sup2": 0x00B2, - "sup3": 0x00B3, - "acute": 0x00B4, - "micro": 0x00B5, - "para": 0x00B6, - "middot": 0x00B7, - "cedil": 0x00B8, - "sup1": 0x00B9, - "ordm": 0x00BA, - "raquo": 0x00BB, - "frac14": 0x00BC, - "frac12": 0x00BD, - "frac34": 0x00BE, - "iquest": 0x00BF, - "Agrave": 0x00C0, - "Aacute": 0x00C1, - "Acirc": 0x00C2, - "Atilde": 0x00C3, - "Auml": 0x00C4, - "Aring": 0x00C5, - "AElig": 0x00C6, - "Ccedil": 0x00C7, - "Egrave": 0x00C8, - "Eacute": 0x00C9, - "Ecirc": 0x00CA, - "Euml": 0x00CB, - "Igrave": 0x00CC, - "Iacute": 0x00CD, - "Icirc": 0x00CE, - "Iuml": 0x00CF, - "ETH": 0x00D0, - "Ntilde": 0x00D1, - "Ograve": 0x00D2, - "Oacute": 0x00D3, - "Ocirc": 0x00D4, - "Otilde": 0x00D5, - "Ouml": 0x00D6, - "times": 0x00D7, - "Oslash": 0x00D8, - "Ugrave": 0x00D9, - "Uacute": 0x00DA, - "Ucirc": 0x00DB, - "Uuml": 0x00DC, - "Yacute": 0x00DD, - "THORN": 0x00DE, - "szlig": 0x00DF, - "agrave": 0x00E0, - "aacute": 0x00E1, - "acirc": 0x00E2, - "atilde": 0x00E3, - "auml": 0x00E4, - "aring": 0x00E5, - "aelig": 0x00E6, - "ccedil": 0x00E7, - "egrave": 0x00E8, - "eacute": 0x00E9, - "ecirc": 0x00EA, - "euml": 0x00EB, - "igrave": 0x00EC, - "iacute": 0x00ED, - "icirc": 0x00EE, - "iuml": 0x00EF, - "eth": 0x00F0, - "ntilde": 0x00F1, - "ograve": 0x00F2, - "oacute": 0x00F3, - "ocirc": 0x00F4, - "otilde": 0x00F5, - "ouml": 0x00F6, - "divide": 0x00F7, - "oslash": 0x00F8, - "ugrave": 0x00F9, - "uacute": 0x00FA, - "ucirc": 0x00FB, - "uuml": 0x00FC, - "yacute": 0x00FD, - "thorn": 0x00FE, - "yuml": 0x00FF, - "OElig": 0x0152, - "oelig": 0x0153, - "Scaron": 0x0160, - "scaron": 0x0161, - "Yuml": 0x0178, - "fnof": 0x0192, - "circ": 0x02C6, - "tilde": 0x02DC, - "Alpha": 0x0391, - "Beta": 0x0392, - "Gamma": 0x0393, - "Delta": 0x0394, - "Epsilon": 0x0395, - "Zeta": 0x0396, - "Eta": 0x0397, - "Theta": 0x0398, - "Iota": 0x0399, - "Kappa": 0x039A, - "Lambda": 0x039B, - "Mu": 0x039C, - "Nu": 0x039D, - "Xi": 0x039E, - "Omicron": 0x039F, - "Pi": 0x03A0, - "Rho": 0x03A1, - "Sigma": 0x03A3, - "Tau": 0x03A4, - "Upsilon": 0x03A5, - "Phi": 0x03A6, - "Chi": 0x03A7, - "Psi": 0x03A8, - "Omega": 0x03A9, - "alpha": 0x03B1, - "beta": 0x03B2, - "gamma": 0x03B3, - "delta": 0x03B4, - "epsilon": 0x03B5, - "zeta": 0x03B6, - "eta": 0x03B7, - "theta": 0x03B8, - "iota": 0x03B9, - "kappa": 0x03BA, - "lambda": 0x03BB, - "mu": 0x03BC, - "nu": 0x03BD, - "xi": 0x03BE, - "omicron": 0x03BF, - "pi": 0x03C0, - "rho": 0x03C1, - "sigmaf": 0x03C2, - "sigma": 0x03C3, - "tau": 0x03C4, - "upsilon": 0x03C5, - "phi": 0x03C6, - "chi": 0x03C7, - "psi": 0x03C8, - "omega": 0x03C9, - "thetasym": 0x03D1, - "upsih": 0x03D2, - "piv": 0x03D6, - "ensp": 0x2002, - "emsp": 0x2003, - "thinsp": 0x2009, - "zwnj": 0x200C, - "zwj": 0x200D, - "lrm": 0x200E, - "rlm": 0x200F, - "ndash": 0x2013, - "mdash": 0x2014, - "lsquo": 0x2018, - "rsquo": 0x2019, - "sbquo": 0x201A, - "ldquo": 0x201C, - "rdquo": 0x201D, - "bdquo": 0x201E, - "dagger": 0x2020, - "Dagger": 0x2021, - "bull": 0x2022, - "hellip": 0x2026, - "permil": 0x2030, - "prime": 0x2032, - "Prime": 0x2033, - "lsaquo": 0x2039, - "rsaquo": 0x203A, - "oline": 0x203E, - "frasl": 0x2044, - "euro": 0x20AC, - "image": 0x2111, - "weierp": 0x2118, - "real": 0x211C, - "trade": 0x2122, - "alefsym": 0x2135, - "larr": 0x2190, - "uarr": 0x2191, - "rarr": 0x2192, - "darr": 0x2193, - "harr": 0x2194, - "crarr": 0x21B5, - "lArr": 0x21D0, - "uArr": 0x21D1, - "rArr": 0x21D2, - "dArr": 0x21D3, - "hArr": 0x21D4, - "forall": 0x2200, - "part": 0x2202, - "exist": 0x2203, - "empty": 0x2205, - "nabla": 0x2207, - "isin": 0x2208, - "notin": 0x2209, - "ni": 0x220B, - "prod": 0x220F, - "sum": 0x2211, - "minus": 0x2212, - "lowast": 0x2217, - "radic": 0x221A, - "prop": 0x221D, - "infin": 0x221E, - "ang": 0x2220, - "and": 0x2227, - "or": 0x2228, - "cap": 0x2229, - "cup": 0x222A, - "int": 0x222B, - "there4": 0x2234, - "sim": 0x223C, - "cong": 0x2245, - "asymp": 0x2248, - "ne": 0x2260, - "equiv": 0x2261, - "le": 0x2264, - "ge": 0x2265, - "sub": 0x2282, - "sup": 0x2283, - "nsub": 0x2284, - "sube": 0x2286, - "supe": 0x2287, - "oplus": 0x2295, - "otimes": 0x2297, - "perp": 0x22A5, - "sdot": 0x22C5, - "lceil": 0x2308, - "rceil": 0x2309, - "lfloor": 0x230A, - "rfloor": 0x230B, - "lang": 0x2329, - "rang": 0x232A, - "loz": 0x25CA, - "spades": 0x2660, - "clubs": 0x2663, - "hearts": 0x2665, - "diams": 0x2666 - }); - } + const entities = createMap({ + "quot": 0x0022, + "amp": 0x0026, + "apos": 0x0027, + "lt": 0x003C, + "gt": 0x003E, + "nbsp": 0x00A0, + "iexcl": 0x00A1, + "cent": 0x00A2, + "pound": 0x00A3, + "curren": 0x00A4, + "yen": 0x00A5, + "brvbar": 0x00A6, + "sect": 0x00A7, + "uml": 0x00A8, + "copy": 0x00A9, + "ordf": 0x00AA, + "laquo": 0x00AB, + "not": 0x00AC, + "shy": 0x00AD, + "reg": 0x00AE, + "macr": 0x00AF, + "deg": 0x00B0, + "plusmn": 0x00B1, + "sup2": 0x00B2, + "sup3": 0x00B3, + "acute": 0x00B4, + "micro": 0x00B5, + "para": 0x00B6, + "middot": 0x00B7, + "cedil": 0x00B8, + "sup1": 0x00B9, + "ordm": 0x00BA, + "raquo": 0x00BB, + "frac14": 0x00BC, + "frac12": 0x00BD, + "frac34": 0x00BE, + "iquest": 0x00BF, + "Agrave": 0x00C0, + "Aacute": 0x00C1, + "Acirc": 0x00C2, + "Atilde": 0x00C3, + "Auml": 0x00C4, + "Aring": 0x00C5, + "AElig": 0x00C6, + "Ccedil": 0x00C7, + "Egrave": 0x00C8, + "Eacute": 0x00C9, + "Ecirc": 0x00CA, + "Euml": 0x00CB, + "Igrave": 0x00CC, + "Iacute": 0x00CD, + "Icirc": 0x00CE, + "Iuml": 0x00CF, + "ETH": 0x00D0, + "Ntilde": 0x00D1, + "Ograve": 0x00D2, + "Oacute": 0x00D3, + "Ocirc": 0x00D4, + "Otilde": 0x00D5, + "Ouml": 0x00D6, + "times": 0x00D7, + "Oslash": 0x00D8, + "Ugrave": 0x00D9, + "Uacute": 0x00DA, + "Ucirc": 0x00DB, + "Uuml": 0x00DC, + "Yacute": 0x00DD, + "THORN": 0x00DE, + "szlig": 0x00DF, + "agrave": 0x00E0, + "aacute": 0x00E1, + "acirc": 0x00E2, + "atilde": 0x00E3, + "auml": 0x00E4, + "aring": 0x00E5, + "aelig": 0x00E6, + "ccedil": 0x00E7, + "egrave": 0x00E8, + "eacute": 0x00E9, + "ecirc": 0x00EA, + "euml": 0x00EB, + "igrave": 0x00EC, + "iacute": 0x00ED, + "icirc": 0x00EE, + "iuml": 0x00EF, + "eth": 0x00F0, + "ntilde": 0x00F1, + "ograve": 0x00F2, + "oacute": 0x00F3, + "ocirc": 0x00F4, + "otilde": 0x00F5, + "ouml": 0x00F6, + "divide": 0x00F7, + "oslash": 0x00F8, + "ugrave": 0x00F9, + "uacute": 0x00FA, + "ucirc": 0x00FB, + "uuml": 0x00FC, + "yacute": 0x00FD, + "thorn": 0x00FE, + "yuml": 0x00FF, + "OElig": 0x0152, + "oelig": 0x0153, + "Scaron": 0x0160, + "scaron": 0x0161, + "Yuml": 0x0178, + "fnof": 0x0192, + "circ": 0x02C6, + "tilde": 0x02DC, + "Alpha": 0x0391, + "Beta": 0x0392, + "Gamma": 0x0393, + "Delta": 0x0394, + "Epsilon": 0x0395, + "Zeta": 0x0396, + "Eta": 0x0397, + "Theta": 0x0398, + "Iota": 0x0399, + "Kappa": 0x039A, + "Lambda": 0x039B, + "Mu": 0x039C, + "Nu": 0x039D, + "Xi": 0x039E, + "Omicron": 0x039F, + "Pi": 0x03A0, + "Rho": 0x03A1, + "Sigma": 0x03A3, + "Tau": 0x03A4, + "Upsilon": 0x03A5, + "Phi": 0x03A6, + "Chi": 0x03A7, + "Psi": 0x03A8, + "Omega": 0x03A9, + "alpha": 0x03B1, + "beta": 0x03B2, + "gamma": 0x03B3, + "delta": 0x03B4, + "epsilon": 0x03B5, + "zeta": 0x03B6, + "eta": 0x03B7, + "theta": 0x03B8, + "iota": 0x03B9, + "kappa": 0x03BA, + "lambda": 0x03BB, + "mu": 0x03BC, + "nu": 0x03BD, + "xi": 0x03BE, + "omicron": 0x03BF, + "pi": 0x03C0, + "rho": 0x03C1, + "sigmaf": 0x03C2, + "sigma": 0x03C3, + "tau": 0x03C4, + "upsilon": 0x03C5, + "phi": 0x03C6, + "chi": 0x03C7, + "psi": 0x03C8, + "omega": 0x03C9, + "thetasym": 0x03D1, + "upsih": 0x03D2, + "piv": 0x03D6, + "ensp": 0x2002, + "emsp": 0x2003, + "thinsp": 0x2009, + "zwnj": 0x200C, + "zwj": 0x200D, + "lrm": 0x200E, + "rlm": 0x200F, + "ndash": 0x2013, + "mdash": 0x2014, + "lsquo": 0x2018, + "rsquo": 0x2019, + "sbquo": 0x201A, + "ldquo": 0x201C, + "rdquo": 0x201D, + "bdquo": 0x201E, + "dagger": 0x2020, + "Dagger": 0x2021, + "bull": 0x2022, + "hellip": 0x2026, + "permil": 0x2030, + "prime": 0x2032, + "Prime": 0x2033, + "lsaquo": 0x2039, + "rsaquo": 0x203A, + "oline": 0x203E, + "frasl": 0x2044, + "euro": 0x20AC, + "image": 0x2111, + "weierp": 0x2118, + "real": 0x211C, + "trade": 0x2122, + "alefsym": 0x2135, + "larr": 0x2190, + "uarr": 0x2191, + "rarr": 0x2192, + "darr": 0x2193, + "harr": 0x2194, + "crarr": 0x21B5, + "lArr": 0x21D0, + "uArr": 0x21D1, + "rArr": 0x21D2, + "dArr": 0x21D3, + "hArr": 0x21D4, + "forall": 0x2200, + "part": 0x2202, + "exist": 0x2203, + "empty": 0x2205, + "nabla": 0x2207, + "isin": 0x2208, + "notin": 0x2209, + "ni": 0x220B, + "prod": 0x220F, + "sum": 0x2211, + "minus": 0x2212, + "lowast": 0x2217, + "radic": 0x221A, + "prop": 0x221D, + "infin": 0x221E, + "ang": 0x2220, + "and": 0x2227, + "or": 0x2228, + "cap": 0x2229, + "cup": 0x222A, + "int": 0x222B, + "there4": 0x2234, + "sim": 0x223C, + "cong": 0x2245, + "asymp": 0x2248, + "ne": 0x2260, + "equiv": 0x2261, + "le": 0x2264, + "ge": 0x2265, + "sub": 0x2282, + "sup": 0x2283, + "nsub": 0x2284, + "sube": 0x2286, + "supe": 0x2287, + "oplus": 0x2295, + "otimes": 0x2297, + "perp": 0x22A5, + "sdot": 0x22C5, + "lceil": 0x2308, + "rceil": 0x2309, + "lfloor": 0x230A, + "rfloor": 0x230B, + "lang": 0x2329, + "rang": 0x232A, + "loz": 0x25CA, + "spades": 0x2660, + "clubs": 0x2663, + "hearts": 0x2665, + "diams": 0x2666 + }); } \ No newline at end of file diff --git a/src/compiler/transformers/module/es2015.ts b/src/compiler/transformers/module/es2015.ts index 93aa108617a5b..5611f89016435 100644 --- a/src/compiler/transformers/module/es2015.ts +++ b/src/compiler/transformers/module/es2015.ts @@ -5,6 +5,14 @@ namespace ts { export function transformES2015Module(context: TransformationContext) { const compilerOptions = context.getCompilerOptions(); + const previousOnEmitNode = context.onEmitNode; + const previousOnSubstituteNode = context.onSubstituteNode; + context.onEmitNode = onEmitNode; + context.onSubstituteNode = onSubstituteNode; + context.enableEmitNotification(SyntaxKind.SourceFile); + context.enableSubstitution(SyntaxKind.Identifier); + + let currentSourceFile: SourceFile; return transformSourceFile; function transformSourceFile(node: SourceFile) { @@ -13,7 +21,27 @@ namespace ts { } if (isExternalModule(node) || compilerOptions.isolatedModules) { - return visitEachChild(node, visitor, context); + const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(node, compilerOptions); + if (externalHelpersModuleName) { + const statements: Statement[] = []; + const statementOffset = addPrologueDirectives(statements, node.statements); + append(statements, + createImportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + createImportClause(/*name*/ undefined, createNamespaceImport(externalHelpersModuleName)), + createLiteral(externalHelpersModuleNameText) + ) + ); + + addRange(statements, visitNodes(node.statements, visitor, isStatement, statementOffset)); + return updateSourceFileNode( + node, + createNodeArray(statements, node.statements)); + } + else { + return visitEachChild(node, visitor, context); + } } return node; @@ -35,5 +63,55 @@ namespace ts { // Elide `export=` as it is not legal with --module ES6 return node.isExportEquals ? undefined : node; } + + // + // Emit Notification + // + + /** + * Hook for node emit. + * + * @param emitContext A context hint for the emitter. + * @param node The node to emit. + * @param emit A callback used to emit the node in the printer. + */ + function onEmitNode(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void { + if (isSourceFile(node)) { + currentSourceFile = node; + previousOnEmitNode(emitContext, node, emitCallback); + currentSourceFile = undefined; + } + else { + previousOnEmitNode(emitContext, node, emitCallback); + } + } + + // + // Substitutions + // + + /** + * Hooks node substitutions. + * + * @param emitContext A context hint for the emitter. + * @param node The node to substitute. + */ + function onSubstituteNode(emitContext: EmitContext, node: Node) { + node = previousOnSubstituteNode(emitContext, node); + if (isIdentifier(node) && emitContext === EmitContext.Expression) { + return substituteExpressionIdentifier(node); + } + return node; + } + + function substituteExpressionIdentifier(node: Identifier): Expression { + if (getEmitFlags(node) & EmitFlags.HelperName) { + const externalHelpersModuleName = getExternalHelpersModuleName(currentSourceFile); + if (externalHelpersModuleName) { + return createPropertyAccess(externalHelpersModuleName, node); + } + } + return node; + } } } diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index b41f1f57b2837..c900b7d20e198 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -60,7 +60,7 @@ namespace ts { } currentSourceFile = node; - currentModuleInfo = moduleInfoMap[getOriginalNodeId(node)] = collectExternalModuleInfo(node, resolver); + currentModuleInfo = moduleInfoMap[getOriginalNodeId(node)] = collectExternalModuleInfo(node, resolver, compilerOptions); // Perform the transformation. const transformModule = transformModuleDelegates[moduleKind] || transformModuleDelegates[ModuleKind.None]; @@ -81,13 +81,14 @@ namespace ts { const statements: Statement[] = []; const statementOffset = addPrologueDirectives(statements, node.statements, /*ensureUseStrict*/ !compilerOptions.noImplicitUseStrict, sourceElementVisitor); + append(statements, visitNode(currentModuleInfo.externalHelpersImportDeclaration, sourceElementVisitor, isStatement, /*optional*/ true)); addRange(statements, visitNodes(node.statements, sourceElementVisitor, isStatement, statementOffset)); addRange(statements, endLexicalEnvironment()); addExportEqualsIfNeeded(statements, /*emitAsReturn*/ false); const updated = updateSourceFileNode(node, createNodeArray(statements, node.statements)); if (currentModuleInfo.hasExportStarsToExportValues) { - setEmitFlags(updated, EmitFlags.EmitExportStar | getEmitFlags(node)); + addEmitHelper(updated, exportStarHelper); } return updated; @@ -110,8 +111,7 @@ namespace ts { * @param node The SourceFile node. */ function transformUMDModule(node: SourceFile) { - const define = createIdentifier("define"); - setEmitFlags(define, EmitFlags.UMDDefine); + const define = createRawExpression(umdHelper); return transformAsynchronousModule(node, define, /*moduleName*/ undefined, /*includeNonAmdDependencies*/ false); } @@ -256,6 +256,7 @@ namespace ts { const statementOffset = addPrologueDirectives(statements, node.statements, /*ensureUseStrict*/ !compilerOptions.noImplicitUseStrict, sourceElementVisitor); // Visit each statement of the module body. + append(statements, visitNode(currentModuleInfo.externalHelpersImportDeclaration, sourceElementVisitor, isStatement, /*optional*/ true)); addRange(statements, visitNodes(node.statements, sourceElementVisitor, isStatement, statementOffset)); // End the lexical environment for the module body @@ -269,7 +270,7 @@ namespace ts { if (currentModuleInfo.hasExportStarsToExportValues) { // If we have any `export * from ...` declarations // we need to inform the emitter to add the __export helper. - setEmitFlags(body, EmitFlags.EmitExportStar); + addEmitHelper(body, exportStarHelper); } return body; @@ -1188,6 +1189,14 @@ namespace ts { * @param node The node to substitute. */ function substituteExpressionIdentifier(node: Identifier): Expression { + if (getEmitFlags(node) & EmitFlags.HelperName) { + const externalHelpersModuleName = getExternalHelpersModuleName(currentSourceFile); + if (externalHelpersModuleName) { + return createPropertyAccess(externalHelpersModuleName, node); + } + return node; + } + if (!isGeneratedIdentifier(node) && !isLocalName(node)) { const exportContainer = resolver.getReferencedExportContainer(node, isExportName(node)); if (exportContainer && exportContainer.kind === SyntaxKind.SourceFile) { @@ -1314,4 +1323,25 @@ namespace ts { } } } + + // emit output for the __export helper function + const exportStarHelper: EmitHelper = { + name: "typescript:export-star", + scoped: true, + text: ` + function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; + }` + }; + + // emit output for the UMD helper function. + const umdHelper = ` + (function (dependencies, factory) { + if (typeof module === 'object' && typeof module.exports === 'object') { + var v = factory(require, exports); if (v !== undefined) module.exports = v; + } + else if (typeof define === 'function' && define.amd) { + define(dependencies, factory); + } + })`; } diff --git a/src/compiler/transformers/module/system.ts b/src/compiler/transformers/module/system.ts index 198060389f845..91e29e0988603 100644 --- a/src/compiler/transformers/module/system.ts +++ b/src/compiler/transformers/module/system.ts @@ -73,7 +73,7 @@ namespace ts { // see comment to 'substitutePostfixUnaryExpression' for more details // Collect information about the external module and dependency groups. - moduleInfo = moduleInfoMap[id] = collectExternalModuleInfo(node, resolver); + moduleInfo = moduleInfoMap[id] = collectExternalModuleInfo(node, resolver, compilerOptions); // Make sure that the name of the 'exports' function does not conflict with // existing identifiers. @@ -82,6 +82,7 @@ namespace ts { // Add the body of the module. const dependencyGroups = collectDependencyGroups(moduleInfo.externalImports); + const moduleBodyBlock = createSystemModuleBody(node, dependencyGroups); const moduleBodyFunction = createFunctionExpression( /*modifiers*/ undefined, /*asteriskToken*/ undefined, @@ -92,7 +93,7 @@ namespace ts { createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, contextObject) ], /*type*/ undefined, - createSystemModuleBody(node, dependencyGroups) + moduleBodyBlock ); // Write the call to `System.register` @@ -115,7 +116,9 @@ namespace ts { ], node.statements) ); - setEmitFlags(updated, getEmitFlags(node) & ~EmitFlags.EmitEmitHelpers); + if (!(compilerOptions.outFile || compilerOptions.out)) { + moveEmitHelpers(updated, moduleBodyBlock, helper => !helper.scoped); + } if (noSubstitution) { noSubstitutionMap[id] = noSubstitution; @@ -236,6 +239,9 @@ namespace ts { ) ); + // Visit the synthetic external helpers import declaration if present + visitNode(moduleInfo.externalHelpersImportDeclaration, sourceElementVisitor, isStatement, /*optional*/ true); + // Visit the statements of the source file, emitting any transformations into // the `executeStatements` array. We do this *before* we fill the `setters` array // as we both emit transformations as well as aggregate some data used when creating @@ -280,9 +286,7 @@ namespace ts { ) ); - const body = createBlock(statements, /*location*/ undefined, /*multiLine*/ true); - setEmitFlags(body, EmitFlags.EmitEmitHelpers); - return body; + return createBlock(statements, /*location*/ undefined, /*multiLine*/ true); } /** @@ -394,7 +398,13 @@ namespace ts { if (localNames) { condition = createLogicalAnd( condition, - createLogicalNot(createHasOwnProperty(localNames, n)) + createLogicalNot( + createCall( + createPropertyAccess(localNames, "hasOwnProperty"), + /*typeArguments*/ undefined, + [n] + ) + ) ); } @@ -1612,6 +1622,14 @@ namespace ts { * @param node The node to substitute. */ function substituteExpressionIdentifier(node: Identifier): Expression { + if (getEmitFlags(node) & EmitFlags.HelperName) { + const externalHelpersModuleName = getExternalHelpersModuleName(currentSourceFile); + if (externalHelpersModuleName) { + return createPropertyAccess(externalHelpersModuleName, node); + } + return node; + } + // When we see an identifier in an expression position that // points to an imported symbol, we should substitute a qualified // reference to the imported symbol if one is needed. diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index a596169ae932f..62b2022b7df55 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -49,7 +49,6 @@ namespace ts { let currentNamespaceContainerName: Identifier; let currentScope: SourceFile | Block | ModuleBlock | CaseBlock; let currentScopeFirstDeclarationsOfName: Map; - let currentExternalHelpersModuleName: Identifier; /** * Keeps track of whether expression substitution has been enabled for specific edge cases. @@ -81,7 +80,13 @@ namespace ts { return node; } - return visitNode(node, visitor, isSourceFile); + currentSourceFile = node; + + const visited = saveStateAndInvoke(node, visitSourceFile); + addEmitHelpers(visited, context.readEmitHelpers()); + + currentSourceFile = undefined; + return visited; } /** @@ -108,6 +113,32 @@ namespace ts { return visited; } + /** + * Performs actions that should always occur immediately before visiting a node. + * + * @param node The node to visit. + */ + function onBeforeVisitNode(node: Node) { + switch (node.kind) { + case SyntaxKind.SourceFile: + case SyntaxKind.CaseBlock: + case SyntaxKind.ModuleBlock: + case SyntaxKind.Block: + currentScope = node; + currentScopeFirstDeclarationsOfName = undefined; + break; + + case SyntaxKind.ClassDeclaration: + case SyntaxKind.FunctionDeclaration: + if (hasModifier(node, ModifierFlags.Ambient)) { + break; + } + + recordEmittedDeclarationInScope(node); + break; + } + } + /** * General-purpose node visitor. * @@ -123,10 +154,7 @@ namespace ts { * @param node The node to visit. */ function visitorWorker(node: Node): VisitResult { - if (node.kind === SyntaxKind.SourceFile) { - return visitSourceFile(node); - } - else if (node.transformFlags & TransformFlags.TypeScript) { + if (node.transformFlags & TransformFlags.TypeScript) { // This node is explicitly marked as TypeScript, so we should transform the node. return visitTypeScript(node); } @@ -253,7 +281,6 @@ namespace ts { return node; } - /** * Branching visitor, visits a TypeScript syntax node. * @@ -443,78 +470,11 @@ namespace ts { } } - /** - * Performs actions that should always occur immediately before visiting a node. - * - * @param node The node to visit. - */ - function onBeforeVisitNode(node: Node) { - switch (node.kind) { - case SyntaxKind.SourceFile: - case SyntaxKind.CaseBlock: - case SyntaxKind.ModuleBlock: - case SyntaxKind.Block: - currentScope = node; - currentScopeFirstDeclarationsOfName = undefined; - break; - - case SyntaxKind.ClassDeclaration: - case SyntaxKind.FunctionDeclaration: - if (hasModifier(node, ModifierFlags.Ambient)) { - break; - } - - recordEmittedDeclarationInScope(node); - break; - } - } - function visitSourceFile(node: SourceFile) { - currentSourceFile = node; - - // ensure "use strict" is emitted in all scenarios in alwaysStrict mode - // There is no need to emit "use strict" in the following cases: - // 1. The file is an external module and target is es2015 or higher - // or 2. The file is an external module and module-kind is es6 or es2015 as such value is not allowed when targeting es5 or lower - if (compilerOptions.alwaysStrict && - !(isExternalModule(node) && (compilerOptions.target >= ScriptTarget.ES2015 || compilerOptions.module === ModuleKind.ES2015))) { - node = ensureUseStrict(node); - } - - // If the source file requires any helpers and is an external module, and - // the importHelpers compiler option is enabled, emit a synthesized import - // statement for the helpers library. - if (node.flags & NodeFlags.EmitHelperFlags - && compilerOptions.importHelpers - && (isExternalModule(node) || compilerOptions.isolatedModules)) { - startLexicalEnvironment(); - const statements: Statement[] = []; - const statementOffset = addPrologueDirectives(statements, node.statements, /*ensureUseStrict*/ false, visitor); - const externalHelpersModuleName = createUniqueName(externalHelpersModuleNameText); - const externalHelpersModuleImport = createImportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - createImportClause(/*name*/ undefined, createNamespaceImport(externalHelpersModuleName)), - createLiteral(externalHelpersModuleNameText)); - - externalHelpersModuleImport.parent = node; - externalHelpersModuleImport.flags &= ~NodeFlags.Synthesized; - statements.push(externalHelpersModuleImport); - - currentExternalHelpersModuleName = externalHelpersModuleName; - addRange(statements, visitNodes(node.statements, sourceElementVisitor, isStatement, statementOffset)); - addRange(statements, endLexicalEnvironment()); - currentExternalHelpersModuleName = undefined; - - node = updateSourceFileNode(node, createNodeArray(statements, node.statements)); - node.externalHelpersModuleName = externalHelpersModuleName; - } - else { - node = visitEachChild(node, sourceElementVisitor, context); - } - - setEmitFlags(node, EmitFlags.EmitEmitHelpers | getEmitFlags(node)); - return node; + const alwaysStrict = compilerOptions.alwaysStrict && !(isExternalModule(node) && moduleKind === ModuleKind.ES2015); + return updateSourceFileNode( + node, + visitLexicalEnvironment(node.statements, sourceElementVisitor, context, /*start*/ 0, alwaysStrict)); } /** @@ -958,15 +918,13 @@ namespace ts { // End the lexical environment. addRange(statements, endLexicalEnvironment()); - return setMultiLine( - createBlock( - createNodeArray( - statements, - /*location*/ constructor ? constructor.body.statements : node.members - ), - /*location*/ constructor ? constructor.body : /*location*/ undefined + return createBlock( + createNodeArray( + statements, + /*location*/ constructor ? constructor.body.statements : node.members ), - true + /*location*/ constructor ? constructor.body : /*location*/ undefined, + /*multiLine*/ true ); } @@ -1424,7 +1382,7 @@ namespace ts { : undefined; const helper = createDecorateHelper( - currentExternalHelpersModuleName, + context, decoratorExpressions, prefix, memberName, @@ -1462,7 +1420,7 @@ namespace ts { const classAlias = classAliases && classAliases[getOriginalNodeId(node)]; const localName = getLocalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true); - const decorate = createDecorateHelper(currentExternalHelpersModuleName, decoratorExpressions, localName); + const decorate = createDecorateHelper(context, decoratorExpressions, localName); const expression = createAssignment(localName, classAlias ? createAssignment(classAlias, decorate) : decorate); setEmitFlags(expression, EmitFlags.NoComments); setSourceMapRange(expression, moveRangePastDecorators(node)); @@ -1490,7 +1448,7 @@ namespace ts { expressions = []; for (const decorator of decorators) { const helper = createParamHelper( - currentExternalHelpersModuleName, + context, transformDecorator(decorator), parameterOffset, /*location*/ decorator.expression); @@ -1520,13 +1478,13 @@ namespace ts { function addOldTypeMetadata(node: Declaration, decoratorExpressions: Expression[]) { if (compilerOptions.emitDecoratorMetadata) { if (shouldAddTypeMetadata(node)) { - decoratorExpressions.push(createMetadataHelper(currentExternalHelpersModuleName, "design:type", serializeTypeOfNode(node))); + decoratorExpressions.push(createMetadataHelper(context, "design:type", serializeTypeOfNode(node))); } if (shouldAddParamTypesMetadata(node)) { - decoratorExpressions.push(createMetadataHelper(currentExternalHelpersModuleName, "design:paramtypes", serializeParameterTypesOfNode(node))); + decoratorExpressions.push(createMetadataHelper(context, "design:paramtypes", serializeParameterTypesOfNode(node))); } if (shouldAddReturnTypeMetadata(node)) { - decoratorExpressions.push(createMetadataHelper(currentExternalHelpersModuleName, "design:returntype", serializeReturnTypeOfNode(node))); + decoratorExpressions.push(createMetadataHelper(context, "design:returntype", serializeReturnTypeOfNode(node))); } } } @@ -1544,7 +1502,7 @@ namespace ts { (properties || (properties = [])).push(createPropertyAssignment("returnType", createArrowFunction(/*modifiers*/ undefined, /*typeParameters*/ undefined, [], /*type*/ undefined, createToken(SyntaxKind.EqualsGreaterThanToken), serializeReturnTypeOfNode(node)))); } if (properties) { - decoratorExpressions.push(createMetadataHelper(currentExternalHelpersModuleName, "design:typeinfo", createObjectLiteral(properties, /*location*/ undefined, /*multiLine*/ true))); + decoratorExpressions.push(createMetadataHelper(context, "design:typeinfo", createObjectLiteral(properties, /*location*/ undefined, /*multiLine*/ true))); } } } @@ -3286,7 +3244,7 @@ namespace ts { function trySubstituteNamespaceExportedName(node: Identifier): Expression { // If this is explicitly a local name, do not substitute. - if (enabledSubstitutions & applicableSubstitutions && !isLocalName(node)) { + if (enabledSubstitutions & applicableSubstitutions && !isGeneratedIdentifier(node) && !isLocalName(node)) { // If we are nested within a namespace declaration, we may need to qualifiy // an identifier that is exported from a merged namespace. const container = resolver.getReferencedExportContainer(node, /*prefixLocals*/ false); @@ -3341,4 +3299,77 @@ namespace ts { : undefined; } } + + const paramHelper: EmitHelper = { + name: "typescript:param", + scoped: false, + priority: 4, + text: ` + var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } + };` + }; + + function createParamHelper(context: TransformationContext, expression: Expression, parameterOffset: number, location?: TextRange) { + context.requestEmitHelper(paramHelper); + return createCall( + getHelperName("__param"), + /*typeArguments*/ undefined, + [ + createLiteral(parameterOffset), + expression + ], + location + ); + } + + const metadataHelper: EmitHelper = { + name: "typescript:metadata", + scoped: false, + priority: 3, + text: ` + var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); + };` + }; + + function createMetadataHelper(context: TransformationContext, metadataKey: string, metadataValue: Expression) { + context.requestEmitHelper(metadataHelper); + return createCall( + getHelperName("__metadata"), + /*typeArguments*/ undefined, + [ + createLiteral(metadataKey), + metadataValue + ] + ); + } + + const decorateHelper: EmitHelper = { + name: "typescript:decorate", + scoped: false, + priority: 2, + text: ` + var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; + };` + }; + + function createDecorateHelper(context: TransformationContext, decoratorExpressions: Expression[], target: Expression, memberName?: Expression, descriptor?: Expression, location?: TextRange) { + context.requestEmitHelper(decorateHelper); + const argumentsArray: Expression[] = []; + argumentsArray.push(createArrayLiteral(decoratorExpressions, /*location*/ undefined, /*multiLine*/ true)); + argumentsArray.push(target); + if (memberName) { + argumentsArray.push(memberName); + if (descriptor) { + argumentsArray.push(descriptor); + } + } + + return createCall(getHelperName("__decorate"), /*typeArguments*/ undefined, argumentsArray, location); + } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index deb6f350c1461..af46cb2173cb9 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -369,6 +369,7 @@ namespace ts { PartiallyEmittedExpression, MergeDeclarationMarker, EndOfDeclarationMarker, + RawExpression, // Enum value count Count, @@ -1523,6 +1524,16 @@ namespace ts { kind: SyntaxKind.EndOfDeclarationMarker; } + /** + * Emits a string of raw text in an expression position. Raw text is never transformed, should + * be ES3 compliant, and should have the same precedence as PrimaryExpression. + */ + /* @internal */ + export interface RawExpression extends PrimaryExpression { + kind: SyntaxKind.RawExpression; + text: string; + } + /** * Marks the beginning of a merged transformed declaration. */ @@ -1562,6 +1573,11 @@ namespace ts { expression: Expression; } + /* @internal */ + export interface PrologueDirective extends ExpressionStatement { + expression: StringLiteral; + } + export interface IfStatement extends Statement { kind: SyntaxKind.IfStatement; expression: Expression; @@ -2162,8 +2178,6 @@ namespace ts { /* @internal */ moduleAugmentations: LiteralExpression[]; /* @internal */ patternAmbientModules?: PatternAmbientModule[]; /* @internal */ ambientModuleNames: string[]; - // The synthesized identifier for an imported external helpers module. - /* @internal */ externalHelpersModuleName?: Identifier; } export interface ScriptReferenceHost { @@ -3626,45 +3640,51 @@ namespace ts { /* @internal */ export interface EmitNode { - flags?: EmitFlags; - commentRange?: TextRange; - sourceMapRange?: TextRange; - tokenSourceMapRanges?: Map; annotatedNodes?: Node[]; // Tracks Parse-tree nodes with EmitNodes for eventual cleanup. - constantValue?: number; + flags?: EmitFlags; // Flags that customize emit + commentRange?: TextRange; // The text range to use when emitting leading or trailing comments + sourceMapRange?: TextRange; // The text range to use when emitting leading or trailing source mappings + tokenSourceMapRanges?: Map; // The text range to use when emitting source mappings for tokens + constantValue?: number; // The constant value of an expression + externalHelpersModuleName?: Identifier; // The local name for an imported helpers module + helpers?: EmitHelper[]; // Emit helpers for the node } /* @internal */ export const enum EmitFlags { - EmitEmitHelpers = 1 << 0, // Any emit helpers should be written to this node. - EmitExportStar = 1 << 1, // The export * helper should be written to this node. - EmitSuperHelper = 1 << 2, // Emit the basic _super helper for async methods. - EmitAdvancedSuperHelper = 1 << 3, // Emit the advanced _super helper for async methods. - UMDDefine = 1 << 4, // This node should be replaced with the UMD define helper. - SingleLine = 1 << 5, // The contents of this node should be emitted on a single line. - AdviseOnEmitNode = 1 << 6, // The printer should invoke the onEmitNode callback when printing this node. - NoSubstitution = 1 << 7, // Disables further substitution of an expression. - CapturesThis = 1 << 8, // The function captures a lexical `this` - NoLeadingSourceMap = 1 << 9, // Do not emit a leading source map location for this node. - NoTrailingSourceMap = 1 << 10, // Do not emit a trailing source map location for this node. + SingleLine = 1 << 0, // The contents of this node should be emitted on a single line. + AdviseOnEmitNode = 1 << 1, // The printer should invoke the onEmitNode callback when printing this node. + NoSubstitution = 1 << 2, // Disables further substitution of an expression. + CapturesThis = 1 << 3, // The function captures a lexical `this` + NoLeadingSourceMap = 1 << 4, // Do not emit a leading source map location for this node. + NoTrailingSourceMap = 1 << 5, // Do not emit a trailing source map location for this node. NoSourceMap = NoLeadingSourceMap | NoTrailingSourceMap, // Do not emit a source map location for this node. - NoNestedSourceMaps = 1 << 11, // Do not emit source map locations for children of this node. - NoTokenLeadingSourceMaps = 1 << 12, // Do not emit leading source map location for token nodes. - NoTokenTrailingSourceMaps = 1 << 13, // Do not emit trailing source map location for token nodes. + NoNestedSourceMaps = 1 << 6, // Do not emit source map locations for children of this node. + NoTokenLeadingSourceMaps = 1 << 7, // Do not emit leading source map location for token nodes. + NoTokenTrailingSourceMaps = 1 << 8, // Do not emit trailing source map location for token nodes. NoTokenSourceMaps = NoTokenLeadingSourceMaps | NoTokenTrailingSourceMaps, // Do not emit source map locations for tokens of this node. - NoLeadingComments = 1 << 14, // Do not emit leading comments for this node. - NoTrailingComments = 1 << 15, // Do not emit trailing comments for this node. + NoLeadingComments = 1 << 9, // Do not emit leading comments for this node. + NoTrailingComments = 1 << 10, // Do not emit trailing comments for this node. NoComments = NoLeadingComments | NoTrailingComments, // Do not emit comments for this node. - NoNestedComments = 1 << 16, - ExportName = 1 << 17, // Ensure an export prefix is added for an identifier that points to an exported declaration with a local name (see SymbolFlags.ExportHasLocal). - LocalName = 1 << 18, // Ensure an export prefix is not added for an identifier that points to an exported declaration. - Indented = 1 << 19, // Adds an explicit extra indentation level for class and function bodies when printing (used to match old emitter). - NoIndentation = 1 << 20, // Do not indent the node. - AsyncFunctionBody = 1 << 21, - ReuseTempVariableScope = 1 << 22, // Reuse the existing temp variable scope during emit. - CustomPrologue = 1 << 23, // Treat the statement as if it were a prologue directive (NOTE: Prologue directives are *not* transformed). - NoHoisting = 1 << 24, // Do not hoist this declaration in --module system - HasEndOfDeclarationMarker = 1 << 25, // Declaration has an associated NotEmittedStatement to mark the end of the declaration + NoNestedComments = 1 << 11, + HelperName = 1 << 12, + ExportName = 1 << 13, // Ensure an export prefix is added for an identifier that points to an exported declaration with a local name (see SymbolFlags.ExportHasLocal). + LocalName = 1 << 14, // Ensure an export prefix is not added for an identifier that points to an exported declaration. + Indented = 1 << 15, // Adds an explicit extra indentation level for class and function bodies when printing (used to match old emitter). + NoIndentation = 1 << 16, // Do not indent the node. + AsyncFunctionBody = 1 << 17, + ReuseTempVariableScope = 1 << 18, // Reuse the existing temp variable scope during emit. + CustomPrologue = 1 << 19, // Treat the statement as if it were a prologue directive (NOTE: Prologue directives are *not* transformed). + NoHoisting = 1 << 20, // Do not hoist this declaration in --module system + HasEndOfDeclarationMarker = 1 << 21, // Declaration has an associated NotEmittedStatement to mark the end of the declaration + } + + /* @internal */ + export interface EmitHelper { + readonly name: string; // A unique name for this helper. + readonly scoped: boolean; // Indicates whether ther helper MUST be emitted in the current scope. + readonly text: string; // ES3-compatible raw script text. + readonly priority?: number; // Helpers with a higher priority are emitted earlier than other helpers on the node. } /* @internal */ @@ -3675,19 +3695,124 @@ namespace ts { Unspecified, // Emitting an otherwise unspecified node } - /** Additional context provided to `visitEachChild` */ /* @internal */ - export interface LexicalEnvironment { + export interface EmitHost extends ScriptReferenceHost { + getSourceFiles(): SourceFile[]; + + /* @internal */ + isSourceFileFromExternalLibrary(file: SourceFile): boolean; + + getCommonSourceDirectory(): string; + getCanonicalFileName(fileName: string): string; + getNewLine(): string; + + isEmitBlocked(emitFileName: string): boolean; + + writeFile: WriteFileCallback; + } + + /* @internal */ + export interface TransformationContext { + getCompilerOptions(): CompilerOptions; + getEmitResolver(): EmitResolver; + getEmitHost(): EmitHost; + /** Starts a new lexical environment. */ startLexicalEnvironment(): void; + /** Suspends the current lexical environment, usually after visiting a parameter list. */ suspendLexicalEnvironment(): void; + + /** Resumes a suspended lexical environment, usually before visiting a function body. */ resumeLexicalEnvironment(): void; /** Ends a lexical environment, returning any declarations. */ endLexicalEnvironment(): Statement[]; + + /** + * Hoists a function declaration to the containing scope. + */ + hoistFunctionDeclaration(node: FunctionDeclaration): void; + + /** + * Hoists a variable declaration to the containing scope. + */ + hoistVariableDeclaration(node: Identifier): void; + + /** + * Records a request for a non-scoped emit helper in the current context. + */ + requestEmitHelper(helper: EmitHelper): void; + + /** + * Gets and resets the requested non-scoped emit helpers. + */ + readEmitHelpers(): EmitHelper[] | undefined; + + /** + * Enables expression substitutions in the pretty printer for the provided SyntaxKind. + */ + enableSubstitution(kind: SyntaxKind): void; + + /** + * Determines whether expression substitutions are enabled for the provided node. + */ + isSubstitutionEnabled(node: Node): boolean; + + /** + * Hook used by transformers to substitute expressions just before they + * are emitted by the pretty printer. + */ + onSubstituteNode?: (emitContext: EmitContext, node: Node) => Node; + + /** + * Enables before/after emit notifications in the pretty printer for the provided + * SyntaxKind. + */ + enableEmitNotification(kind: SyntaxKind): void; + + /** + * Determines whether before/after emit notifications should be raised in the pretty + * printer when it emits a node. + */ + isEmitNotificationEnabled(node: Node): boolean; + + /** + * Hook used to allow transformers to capture state before or after + * the printer emits a node. + */ + onEmitNode?: (emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void) => void; + } + + /* @internal */ + export interface TransformationResult { + /** + * Gets the transformed source files. + */ + transformed: SourceFile[]; + + /** + * Emits the substitute for a node, if one is available; otherwise, emits the node. + * + * @param emitContext The current emit context. + * @param node The node to substitute. + * @param emitCallback A callback used to emit the node or its substitute. + */ + emitNodeWithSubstitution(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void; + + /** + * Emits a node with possible notification. + * + * @param emitContext The current emit context. + * @param node The node to emit. + * @param emitCallback A callback used to emit the node. + */ + emitNodeWithNotification(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void; } + /* @internal */ + export type Transformer = (context: TransformationContext) => (node: SourceFile) => SourceFile; + export interface TextSpan { start: number; length: number; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 455ca0ff740fe..bfabb3b1e4a9d 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -28,21 +28,6 @@ namespace ts { string(): string; } - export interface EmitHost extends ScriptReferenceHost { - getSourceFiles(): SourceFile[]; - - /* @internal */ - isSourceFileFromExternalLibrary(file: SourceFile): boolean; - - getCommonSourceDirectory(): string; - getCanonicalFileName(fileName: string): string; - getNewLine(): string; - - isEmitBlocked(emitFileName: string): boolean; - - writeFile: WriteFileCallback; - } - // Pool writers to avoid needing to allocate them for every symbol we write. const stringWriters: StringSymbolWriter[] = []; export function getSingleLineStringWriter(): StringSymbolWriter { @@ -626,8 +611,9 @@ namespace ts { return n.kind === SyntaxKind.CallExpression && (n).expression.kind === SyntaxKind.SuperKeyword; } - export function isPrologueDirective(node: Node): boolean { - return node.kind === SyntaxKind.ExpressionStatement && (node).expression.kind === SyntaxKind.StringLiteral; + export function isPrologueDirective(node: Node): node is PrologueDirective { + return node.kind === SyntaxKind.ExpressionStatement + && (node).expression.kind === SyntaxKind.StringLiteral; } export function getLeadingCommentRangesOfNode(node: Node, sourceFileOfNode: SourceFile) { @@ -2148,6 +2134,7 @@ namespace ts { case SyntaxKind.TemplateExpression: case SyntaxKind.ParenthesizedExpression: case SyntaxKind.OmittedExpression: + case SyntaxKind.RawExpression: return 19; case SyntaxKind.TaggedTemplateExpression: @@ -3572,153 +3559,6 @@ namespace ts { return positionIsSynthesized(range.pos) ? -1 : skipTrivia(sourceFile.text, range.pos); } - export interface ExternalModuleInfo { - externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[]; // imports of other external modules - exportSpecifiers: Map; // export specifiers by name - exportedBindings: Map; // exported names of local declarations - exportedNames: Identifier[]; // all exported names local to module - exportEquals: ExportAssignment | undefined; // an export= declaration if one was present - hasExportStarsToExportValues: boolean; // whether this module contains export* - } - - export function collectExternalModuleInfo(sourceFile: SourceFile, resolver: EmitResolver): ExternalModuleInfo { - const externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[] = []; - const exportSpecifiers = createMap(); - const exportedBindings = createMap(); - const uniqueExports = createMap(); - let hasExportDefault = false; - let exportEquals: ExportAssignment = undefined; - let hasExportStarsToExportValues = false; - for (const node of sourceFile.statements) { - switch (node.kind) { - case SyntaxKind.ImportDeclaration: - // import "mod" - // import x from "mod" - // import * as x from "mod" - // import { x, y } from "mod" - externalImports.push(node); - break; - - case SyntaxKind.ImportEqualsDeclaration: - if ((node).moduleReference.kind === SyntaxKind.ExternalModuleReference) { - // import x = require("mod") - externalImports.push(node); - } - - break; - - case SyntaxKind.ExportDeclaration: - if ((node).moduleSpecifier) { - if (!(node).exportClause) { - // export * from "mod" - externalImports.push(node); - hasExportStarsToExportValues = true; - } - else { - // export { x, y } from "mod" - externalImports.push(node); - } - } - else { - // export { x, y } - for (const specifier of (node).exportClause.elements) { - if (!uniqueExports[specifier.name.text]) { - const name = specifier.propertyName || specifier.name; - multiMapAdd(exportSpecifiers, name.text, specifier); - - const decl = resolver.getReferencedImportDeclaration(name) - || resolver.getReferencedValueDeclaration(name); - - if (decl) { - multiMapAdd(exportedBindings, getOriginalNodeId(decl), specifier.name); - } - - uniqueExports[specifier.name.text] = specifier.name; - } - } - } - break; - - case SyntaxKind.ExportAssignment: - if ((node).isExportEquals && !exportEquals) { - // export = x - exportEquals = node; - } - break; - - case SyntaxKind.VariableStatement: - if (hasModifier(node, ModifierFlags.Export)) { - for (const decl of (node).declarationList.declarations) { - collectExportedVariableInfo(decl, uniqueExports); - } - } - break; - - case SyntaxKind.FunctionDeclaration: - if (hasModifier(node, ModifierFlags.Export)) { - if (hasModifier(node, ModifierFlags.Default)) { - // export default function() { } - if (!hasExportDefault) { - multiMapAdd(exportedBindings, getOriginalNodeId(node), getDeclarationName(node)); - hasExportDefault = true; - } - } - else { - // export function x() { } - const name = (node).name; - if (!uniqueExports[name.text]) { - multiMapAdd(exportedBindings, getOriginalNodeId(node), name); - uniqueExports[name.text] = name; - } - } - } - break; - - case SyntaxKind.ClassDeclaration: - if (hasModifier(node, ModifierFlags.Export)) { - if (hasModifier(node, ModifierFlags.Default)) { - // export default class { } - if (!hasExportDefault) { - multiMapAdd(exportedBindings, getOriginalNodeId(node), getDeclarationName(node)); - hasExportDefault = true; - } - } - else { - // export class x { } - const name = (node).name; - if (!uniqueExports[name.text]) { - multiMapAdd(exportedBindings, getOriginalNodeId(node), name); - uniqueExports[name.text] = name; - } - } - } - break; - } - } - - let exportedNames: Identifier[]; - for (const key in uniqueExports) { - exportedNames = ts.append(exportedNames, uniqueExports[key]); - } - - return { externalImports, exportSpecifiers, exportEquals, hasExportStarsToExportValues, exportedBindings, exportedNames }; - } - - function collectExportedVariableInfo(decl: VariableDeclaration | BindingElement, uniqueExports: Map) { - if (isBindingPattern(decl.name)) { - for (const element of decl.name.elements) { - if (!isOmittedExpression(element)) { - collectExportedVariableInfo(element, uniqueExports); - } - } - } - else if (!isGeneratedIdentifier(decl.name)) { - if (!uniqueExports[decl.name.text]) { - uniqueExports[decl.name.text] = decl.name; - } - } - } - /** * Determines whether a name was originally the declaration name of an enum or namespace * declaration. @@ -4086,7 +3926,8 @@ namespace ts { || kind === SyntaxKind.ThisKeyword || kind === SyntaxKind.TrueKeyword || kind === SyntaxKind.SuperKeyword - || kind === SyntaxKind.NonNullExpression; + || kind === SyntaxKind.NonNullExpression + || kind === SyntaxKind.RawExpression; } export function isLeftHandSideExpression(node: Node): node is LeftHandSideExpression { @@ -4116,6 +3957,7 @@ namespace ts { || kind === SyntaxKind.SpreadElement || kind === SyntaxKind.AsExpression || kind === SyntaxKind.OmittedExpression + || kind === SyntaxKind.RawExpression || isUnaryExpressionKind(kind); } diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index e75fdcb488bdc..43e01ca56bb59 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -669,9 +669,12 @@ namespace ts { * Starts a new lexical environment and visits a statement list, ending the lexical environment * and merging hoisted declarations upon completion. */ - export function visitLexicalEnvironment(statements: NodeArray, visitor: (node: Node) => VisitResult, context: LexicalEnvironment, start?: number) { + export function visitLexicalEnvironment(statements: NodeArray, visitor: (node: Node) => VisitResult, context: TransformationContext, start?: number, ensureUseStrict?: boolean) { context.startLexicalEnvironment(); statements = visitNodes(statements, visitor, isStatement, start); + if (ensureUseStrict && !startsWithUseStrict(statements)) { + statements = createNodeArray([createStatement(createLiteral("use strict")), ...statements], statements); + } const declarations = context.endLexicalEnvironment(); return createNodeArray(concatenate(statements, declarations), statements); } @@ -680,9 +683,9 @@ namespace ts { * Starts a new lexical environment and visits a parameter list, suspending the lexical * environment upon completion. */ - export function visitParameterList(nodes: NodeArray, visitor: (node: Node) => VisitResult, context: LexicalEnvironment) { + export function visitParameterList(nodes: NodeArray, visitor: (node: Node) => VisitResult, context: TransformationContext) { context.startLexicalEnvironment(); - const updated = visitNodes(nodes, visitor, isParameter); + const updated = visitNodes(nodes, visitor, isParameterDeclaration); context.suspendLexicalEnvironment(); return updated; } @@ -691,21 +694,22 @@ namespace ts { * Resumes a suspended lexical environment and visits a function body, ending the lexical * environment and merging hoisted declarations upon completion. */ - export function visitFunctionBody(node: FunctionBody, visitor: (node: Node) => VisitResult, context: LexicalEnvironment): FunctionBody; + export function visitFunctionBody(node: FunctionBody, visitor: (node: Node) => VisitResult, context: TransformationContext): FunctionBody; /** * Resumes a suspended lexical environment and visits a concise body, ending the lexical * environment and merging hoisted declarations upon completion. */ - export function visitFunctionBody(node: ConciseBody, visitor: (node: Node) => VisitResult, context: LexicalEnvironment): ConciseBody; - export function visitFunctionBody(node: ConciseBody, visitor: (node: Node) => VisitResult, context: LexicalEnvironment) { + export function visitFunctionBody(node: ConciseBody, visitor: (node: Node) => VisitResult, context: TransformationContext): ConciseBody; + export function visitFunctionBody(node: ConciseBody, visitor: (node: Node) => VisitResult, context: TransformationContext): ConciseBody { context.resumeLexicalEnvironment(); - const visited = visitNode(node, visitor, isConciseBody); + const updated = visitNode(node, visitor, isConciseBody); const declarations = context.endLexicalEnvironment(); if (some(declarations)) { - const block = convertToFunctionBody(visited); - return updateBlock(block, createNodeArray(concatenate(block.statements, declarations), block.statements)); + const block = convertToFunctionBody(updated); + const statements = mergeLexicalEnvironment(block.statements, declarations); + return updateBlock(block, statements); } - return visited; + return updated; } /** @@ -715,8 +719,8 @@ namespace ts { * @param visitor The callback used to visit each child. * @param context A lexical environment context for the visitor. */ - export function visitEachChild(node: T, visitor: (node: Node) => VisitResult, context: LexicalEnvironment): T; - export function visitEachChild(node: Node, visitor: (node: Node) => VisitResult, context: LexicalEnvironment): Node { + export function visitEachChild(node: T, visitor: (node: Node) => VisitResult, context: TransformationContext): T; + export function visitEachChild(node: Node, visitor: (node: Node) => VisitResult, context: TransformationContext): Node { if (node === undefined) { return undefined; } @@ -1042,7 +1046,7 @@ namespace ts { visitNodes((node).typeParameters, visitor, isTypeParameter), visitParameterList((node).parameters, visitor, context), visitNode((node).type, visitor, isTypeNode, /*optional*/ true), - visitFunctionBody((node).body, visitor, context)); + visitFunctionBody((node).body, visitor, context)); case SyntaxKind.ClassDeclaration: return updateClassDeclaration(node, @@ -1209,6 +1213,24 @@ namespace ts { // return node; } + /** + * Merges generated lexical declarations into a new statement list. + */ + export function mergeLexicalEnvironment(statements: NodeArray, declarations: Statement[]): NodeArray; + /** + * Appends generated lexical declarations to an array of statements. + */ + export function mergeLexicalEnvironment(statements: Statement[], declarations: Statement[]): Statement[]; + export function mergeLexicalEnvironment(statements: Statement[], declarations: Statement[]) { + if (!some(declarations)) { + return statements; + } + return isNodeArray(statements) + ? createNodeArray(concatenate(statements, declarations), statements) + : addRange(statements, declarations); + } + + /** * Merges generated lexical declarations into the FunctionBody of a non-arrow function-like declaration. * diff --git a/tests/baselines/reference/importHelpersNoHelpers.js b/tests/baselines/reference/importHelpersNoHelpers.js index 690a6c05f17f8..a9c2deb76b6b2 100644 --- a/tests/baselines/reference/importHelpersNoHelpers.js +++ b/tests/baselines/reference/importHelpersNoHelpers.js @@ -67,8 +67,8 @@ C = tslib_1.__decorate([ tslib_1.__metadata("design:paramtypes", []) ], C); var o = { a: 1 }; -var y = __assign({}, o); -var x = __rest(y, []); +var y = tslib_1.__assign({}, o); +var x = tslib_1.__rest(y, []); //// [script.js] var __extends = (this && this.__extends) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];