diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index c2bfce10e57d6..a6f5993f6c88d 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -518,7 +518,6 @@ namespace ts { hasExplicitReturn = false; bindChildren(node); // Reset all reachability check related flags on node (for incremental scenarios) - // Reset all emit helper flags on node (for incremental scenarios) node.flags &= ~NodeFlags.ReachabilityAndEmitFlags; if (!(currentFlow.flags & FlowFlags.Unreachable) && containerFlags & ContainerFlags.IsFunctionLike && nodeIsPresent((node).body)) { node.flags |= NodeFlags.HasImplicitReturn; @@ -1950,9 +1949,6 @@ namespace ts { return bindParameter(node); case SyntaxKind.VariableDeclaration: case SyntaxKind.BindingElement: - if ((node as BindingElement).dotDotDotToken && node.parent.kind === SyntaxKind.ObjectBindingPattern) { - emitFlags |= NodeFlags.HasRestAttribute; - } return bindVariableDeclarationOrBindingElement(node); case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: @@ -1980,7 +1976,6 @@ namespace ts { } root = root.parent; } - emitFlags |= hasRest ? NodeFlags.HasRestAttribute : NodeFlags.HasSpreadAttribute; return; case SyntaxKind.CallSignature: @@ -2236,15 +2231,6 @@ namespace ts { } function bindClassLikeDeclaration(node: ClassLikeDeclaration) { - if (!isDeclarationFile(file) && !isInAmbientContext(node)) { - if (getClassExtendsHeritageClauseElement(node) !== undefined) { - emitFlags |= NodeFlags.HasClassExtends; - } - if (nodeIsDecorated(node)) { - emitFlags |= NodeFlags.HasDecorators; - } - } - if (node.kind === SyntaxKind.ClassDeclaration) { bindBlockScopedDeclaration(node, SymbolFlags.Class, SymbolFlags.ClassExcludes); } @@ -2314,12 +2300,6 @@ namespace ts { } function bindParameter(node: ParameterDeclaration) { - if (!isDeclarationFile(file) && - !isInAmbientContext(node) && - nodeIsDecorated(node)) { - emitFlags |= (NodeFlags.HasDecorators | NodeFlags.HasParamDecorators); - } - if (inStrictMode) { // It is a SyntaxError if the identifier eval or arguments appears within a FormalParameterList of a // strict mode FunctionLikeDeclaration or FunctionExpression(13.1) @@ -2377,9 +2357,6 @@ namespace ts { if (isAsyncFunctionLike(node)) { emitFlags |= NodeFlags.HasAsyncFunctions; } - if (nodeIsDecorated(node)) { - emitFlags |= NodeFlags.HasDecorators; - } } if (currentFlow && isObjectLiteralOrClassExpressionMethod(node)) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 367f64a9b90c6..3842532257243 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -38,6 +38,8 @@ namespace ts { // is because diagnostics can be quite expensive, and we want to allow hosts to bail out if // they no longer need the information (for example, if the user started editing again). let cancellationToken: CancellationToken; + let requestedExternalEmitHelpers: ExternalEmitHelpers; + let externalHelpersModule: Symbol; const Symbol = objectAllocator.getSymbolConstructor(); const Type = objectAllocator.getTypeConstructor(); @@ -11461,6 +11463,9 @@ namespace ts { member = prop; } else if (memberDecl.kind === SyntaxKind.SpreadAssignment) { + if (languageVersion < ScriptTarget.ESNext) { + checkExternalEmitHelpers(memberDecl, ExternalEmitHelpers.Assign); + } if (propertiesArray.length > 0) { spread = getSpreadType(spread, createObjectLiteralType(), /*isFromObjectLiteral*/ true); propertiesArray = []; @@ -11654,6 +11659,9 @@ namespace ts { } function checkJsxSpreadAttribute(node: JsxSpreadAttribute, elementAttributesType: Type, nameTable: Map) { + if (compilerOptions.jsx === JsxEmit.React) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Assign); + } const type = checkExpression(node.expression); const props = getPropertiesOfType(type); for (const prop of props) { @@ -14419,6 +14427,9 @@ namespace ts { } } else if (property.kind === SyntaxKind.SpreadAssignment) { + if (languageVersion < ScriptTarget.ESNext) { + checkExternalEmitHelpers(property, ExternalEmitHelpers.Rest); + } const nonRestNames: PropertyName[] = []; if (allProperties) { for (let i = 0; i < allProperties.length - 1; i++) { @@ -15331,6 +15342,13 @@ namespace ts { checkGrammarFunctionLikeDeclaration(node); } + if (isAsyncFunctionLike(node) && languageVersion < ScriptTarget.ES2017) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Awaiter); + if (languageVersion < ScriptTarget.ES2015) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Generator); + } + } + checkTypeParameters(node.typeParameters); forEach(node.parameters, checkParameter); @@ -16466,7 +16484,15 @@ namespace ts { error(node, Diagnostics.Experimental_support_for_decorators_is_a_feature_that_is_subject_to_change_in_a_future_release_Set_the_experimentalDecorators_option_to_remove_this_warning); } + const firstDecorator = node.decorators[0]; + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Decorate); + if (node.kind === SyntaxKind.Parameter) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Param); + } + if (compilerOptions.emitDecoratorMetadata) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Metadata); + // we only need to perform these checks if we are emitting serialized type metadata for the target of a decorator. switch (node.kind) { case SyntaxKind.ClassDeclaration: @@ -16853,7 +16879,7 @@ namespace ts { } function checkCollisionWithGlobalPromiseInGeneratedCode(node: Node, name: Identifier): void { - if (!needCollisionCheckForIdentifier(node, name, "Promise")) { + if (languageVersion >= ScriptTarget.ES2017 || !needCollisionCheckForIdentifier(node, name, "Promise")) { return; } @@ -17030,6 +17056,9 @@ namespace ts { } if (node.kind === SyntaxKind.BindingElement) { + if (node.parent.kind === SyntaxKind.ObjectBindingPattern && languageVersion < ScriptTarget.ESNext) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Rest); + } // check computed properties inside property names of binding elements if (node.propertyName && node.propertyName.kind === SyntaxKind.ComputedPropertyName) { checkComputedPropertyName(node.propertyName); @@ -17947,6 +17976,10 @@ namespace ts { const baseTypeNode = getClassExtendsHeritageClauseElement(node); if (baseTypeNode) { + if (languageVersion < ScriptTarget.ES2015) { + checkExternalEmitHelpers(baseTypeNode.parent, ExternalEmitHelpers.Extends); + } + const baseTypes = getBaseTypes(type); if (baseTypes.length && produceDiagnostics) { const baseType = baseTypes[0]; @@ -20387,8 +20420,6 @@ namespace ts { // Initialize global symbol table let augmentations: LiteralExpression[][]; - let requestedExternalEmitHelpers: NodeFlags = 0; - let firstFileRequestingExternalHelpers: SourceFile; for (const file of host.getSourceFiles()) { if (!isExternalOrCommonJsModule(file)) { mergeSymbolTable(globals, file.locals); @@ -20408,15 +20439,6 @@ namespace ts { } } } - if ((compilerOptions.isolatedModules || isExternalModule(file)) && !file.isDeclarationFile) { - const fileRequestedExternalEmitHelpers = file.flags & NodeFlags.EmitHelperFlags; - if (fileRequestedExternalEmitHelpers) { - requestedExternalEmitHelpers |= fileRequestedExternalEmitHelpers; - if (firstFileRequestingExternalHelpers === undefined) { - firstFileRequestingExternalHelpers = file; - } - } - } } if (augmentations) { @@ -20482,57 +20504,51 @@ namespace ts { const symbol = getGlobalSymbol("ReadonlyArray", SymbolFlags.Type, /*diagnostic*/ undefined); globalReadonlyArrayType = symbol && getTypeOfGlobalSymbol(symbol, /*arity*/ 1); anyReadonlyArrayType = globalReadonlyArrayType ? createTypeFromGenericGlobalType(globalReadonlyArrayType, [anyType]) : anyArrayType; + } - // If we have specified that we are importing helpers, we should report global - // errors if we cannot resolve the helpers external module, or if it does not have - // the necessary helpers exported. - if (compilerOptions.importHelpers && firstFileRequestingExternalHelpers) { - // Find the first reference to the helpers module. - const helpersModule = resolveExternalModule( - firstFileRequestingExternalHelpers, - externalHelpersModuleNameText, - Diagnostics.Cannot_find_module_0, - /*errorNode*/ undefined); - - // If we found the module, report errors if it does not have the necessary exports. - if (helpersModule) { - const exports = helpersModule.exports; - if (requestedExternalEmitHelpers & NodeFlags.HasClassExtends && languageVersion < ScriptTarget.ES2015) { - verifyHelperSymbol(exports, "__extends", SymbolFlags.Value); - } - if (requestedExternalEmitHelpers & NodeFlags.HasSpreadAttribute && - (languageVersion < ScriptTarget.ESNext || compilerOptions.jsx === JsxEmit.React)) { - verifyHelperSymbol(exports, "__assign", SymbolFlags.Value); - } - if (languageVersion < ScriptTarget.ESNext && requestedExternalEmitHelpers & NodeFlags.HasRestAttribute) { - verifyHelperSymbol(exports, "__rest", SymbolFlags.Value); - } - if (requestedExternalEmitHelpers & NodeFlags.HasDecorators) { - verifyHelperSymbol(exports, "__decorate", SymbolFlags.Value); - if (compilerOptions.emitDecoratorMetadata) { - verifyHelperSymbol(exports, "__metadata", SymbolFlags.Value); - } - } - if (requestedExternalEmitHelpers & NodeFlags.HasParamDecorators) { - verifyHelperSymbol(exports, "__param", SymbolFlags.Value); - } - if (requestedExternalEmitHelpers & NodeFlags.HasAsyncFunctions) { - verifyHelperSymbol(exports, "__awaiter", SymbolFlags.Value); - if (languageVersion < ScriptTarget.ES2015) { - verifyHelperSymbol(exports, "__generator", SymbolFlags.Value); + function checkExternalEmitHelpers(location: Node, helpers: ExternalEmitHelpers) { + if ((requestedExternalEmitHelpers & helpers) !== helpers && compilerOptions.importHelpers) { + const sourceFile = getSourceFileOfNode(location); + if (isEffectiveExternalModule(sourceFile, compilerOptions)) { + const helpersModule = resolveHelpersModule(sourceFile, location); + if (helpersModule !== unknownSymbol) { + const uncheckedHelpers = helpers & ~requestedExternalEmitHelpers; + for (let helper = ExternalEmitHelpers.FirstEmitHelper; helper <= ExternalEmitHelpers.LastEmitHelper; helper <<= 1) { + if (uncheckedHelpers & helper) { + const name = getHelperName(helper); + const symbol = getSymbol(helpersModule.exports, escapeIdentifier(name), SymbolFlags.Value); + if (!symbol) { + error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_but_module_0_has_no_exported_member_1, externalHelpersModuleNameText, name); + } + } } } + requestedExternalEmitHelpers |= helpers; } } } - function verifyHelperSymbol(symbols: SymbolTable, name: string, meaning: SymbolFlags) { - const symbol = getSymbol(symbols, escapeIdentifier(name), meaning); - if (!symbol) { - error(/*location*/ undefined, Diagnostics.Module_0_has_no_exported_member_1, externalHelpersModuleNameText, name); + function getHelperName(helper: ExternalEmitHelpers) { + switch (helper) { + case ExternalEmitHelpers.Extends: return "__extends"; + case ExternalEmitHelpers.Assign: return "__assign"; + case ExternalEmitHelpers.Rest: return "__rest"; + case ExternalEmitHelpers.Decorate: return "__decorate"; + case ExternalEmitHelpers.Metadata: return "__metadata"; + case ExternalEmitHelpers.Param: return "__param"; + case ExternalEmitHelpers.Awaiter: return "__awaiter"; + case ExternalEmitHelpers.Generator: return "__generator"; } } + function resolveHelpersModule(node: SourceFile, errorNode: Node) { + if (!externalHelpersModule) { + externalHelpersModule = resolveExternalModule(node, externalHelpersModuleNameText, Diagnostics.This_syntax_requires_an_imported_helper_but_module_0_cannot_be_found, errorNode) || unknownSymbol; + } + return externalHelpersModule; + } + + function createInstantiatedPromiseLikeType(): ObjectType { const promiseLikeType = getGlobalPromiseLikeType(); if (promiseLikeType !== emptyGenericType) { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 8f3b9beb888ad..007986e23bee0 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1027,6 +1027,10 @@ "category": "Error", "code": 2342 }, + "This syntax requires an imported helper named '{1}', but module '{0}' has no exported member '{1}'.": { + "category": "Error", + "code": 2343 + }, "Type '{0}' does not satisfy the constraint '{1}'.": { "category": "Error", "code": 2344 @@ -1067,6 +1071,10 @@ "category": "Error", "code": 2353 }, + "This syntax requires an imported helper but module '{0}' cannot be found.": { + "category": "Error", + "code": 2354 + }, "A function whose declared type is neither 'void' nor 'any' must return a value.": { "category": "Error", "code": 2355 diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 219e7efa90a23..bc400df257d9e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -418,26 +418,20 @@ namespace ts { HasImplicitReturn = 1 << 7, // If function implicitly returns on one of codepaths (initialized by binding) HasExplicitReturn = 1 << 8, // If function has explicit reachable return on one of codepaths (initialized by binding) GlobalAugmentation = 1 << 9, // Set if module declaration is an augmentation for the global scope - HasClassExtends = 1 << 10, // If the file has a non-ambient class with an extends clause in ES5 or lower (initialized by binding) - HasDecorators = 1 << 11, // If the file has decorators (initialized by binding) - HasParamDecorators = 1 << 12, // If the file has parameter decorators (initialized by binding) - HasAsyncFunctions = 1 << 13, // If the file has async functions (initialized by binding) - HasSpreadAttribute = 1 << 14, // If the file as JSX spread attributes (initialized by binding) - HasRestAttribute = 1 << 15, // If the file has object destructure elements - DisallowInContext = 1 << 16, // If node was parsed in a context where 'in-expressions' are not allowed - YieldContext = 1 << 17, // If node was parsed in the 'yield' context created when parsing a generator - DecoratorContext = 1 << 18, // If node was parsed as part of a decorator - AwaitContext = 1 << 19, // If node was parsed in the 'await' context created when parsing an async function - ThisNodeHasError = 1 << 20, // If the parser encountered an error when parsing the code that created this node - JavaScriptFile = 1 << 21, // If node was parsed in a JavaScript - ThisNodeOrAnySubNodesHasError = 1 << 22, // If this node or any of its children had an error - HasAggregatedChildData = 1 << 23, // If we've computed data from children and cached it in this node + HasAsyncFunctions = 1 << 10, // If the file has async functions (initialized by binding) + DisallowInContext = 1 << 11, // If node was parsed in a context where 'in-expressions' are not allowed + YieldContext = 1 << 12, // If node was parsed in the 'yield' context created when parsing a generator + DecoratorContext = 1 << 13, // If node was parsed as part of a decorator + AwaitContext = 1 << 14, // If node was parsed in the 'await' context created when parsing an async function + ThisNodeHasError = 1 << 15, // If the parser encountered an error when parsing the code that created this node + JavaScriptFile = 1 << 16, // If node was parsed in a JavaScript + ThisNodeOrAnySubNodesHasError = 1 << 17, // If this node or any of its children had an error + HasAggregatedChildData = 1 << 18, // If we've computed data from children and cached it in this node BlockScoped = Let | Const, ReachabilityCheckFlags = HasImplicitReturn | HasExplicitReturn, - EmitHelperFlags = HasClassExtends | HasDecorators | HasParamDecorators | HasAsyncFunctions | HasSpreadAttribute | HasRestAttribute, - ReachabilityAndEmitFlags = ReachabilityCheckFlags | EmitHelperFlags, + ReachabilityAndEmitFlags = ReachabilityCheckFlags | HasAsyncFunctions, // Parsing context flags ContextFlags = DisallowInContext | YieldContext | DecoratorContext | AwaitContext | JavaScriptFile, @@ -3698,6 +3692,25 @@ namespace ts { readonly priority?: number; // Helpers with a higher priority are emitted earlier than other helpers on the node. } + /** + * Used by the checker, this enum keeps track of external emit helpers that should be type + * checked. + */ + /* @internal */ + export const enum ExternalEmitHelpers { + Extends = 1 << 0, // __extends (used by the ES2015 class transformation) + Assign = 1 << 1, // __assign (used by Jsx and ESNext object spread transformations) + Rest = 1 << 2, // __rest (used by ESNext object rest transformation) + Decorate = 1 << 3, // __decorate (used by TypeScript decorators transformation) + Metadata = 1 << 4, // __metadata (used by TypeScript decorators transformation) + Param = 1 << 5, // __param (used by TypeScript decorators transformation) + Awaiter = 1 << 6, // __awaiter (used by ES2017 async functions transformation) + Generator = 1 << 7, // __generator (used by ES2015 generator transformation) + + FirstEmitHelper = Extends, + LastEmitHelper = Generator + } + /* @internal */ export const enum EmitContext { SourceFile, // Emitting a SourceFile diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 363ddf0de801c..4fa0c60dce7c5 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -423,6 +423,10 @@ namespace ts { return false; } + export function isEffectiveExternalModule(node: SourceFile, compilerOptions: CompilerOptions) { + return isExternalModule(node) || compilerOptions.isolatedModules; + } + export function isBlockScope(node: Node, parentNode: Node) { switch (node.kind) { case SyntaxKind.SourceFile: diff --git a/tests/baselines/reference/importHelpersNoHelpers.errors.txt b/tests/baselines/reference/importHelpersNoHelpers.errors.txt index fa9a935c8d811..a0c089a9ddc26 100644 --- a/tests/baselines/reference/importHelpersNoHelpers.errors.txt +++ b/tests/baselines/reference/importHelpersNoHelpers.errors.txt @@ -1,32 +1,38 @@ -error TS2305: Module 'tslib' has no exported member '__assign'. -error TS2305: Module 'tslib' has no exported member '__decorate'. -error TS2305: Module 'tslib' has no exported member '__extends'. -error TS2305: Module 'tslib' has no exported member '__metadata'. -error TS2305: Module 'tslib' has no exported member '__param'. -error TS2305: Module 'tslib' has no exported member '__rest'. +tests/cases/compiler/external.ts(2,16): error TS2343: This syntax requires an imported helper named '__extends', but module 'tslib' has no exported member '__extends'. +tests/cases/compiler/external.ts(6,1): error TS2343: This syntax requires an imported helper named '__decorate', but module 'tslib' has no exported member '__decorate'. +tests/cases/compiler/external.ts(6,1): error TS2343: This syntax requires an imported helper named '__metadata', but module 'tslib' has no exported member '__metadata'. +tests/cases/compiler/external.ts(8,12): error TS2343: This syntax requires an imported helper named '__param', but module 'tslib' has no exported member '__param'. +tests/cases/compiler/external.ts(13,13): error TS2343: This syntax requires an imported helper named '__assign', but module 'tslib' has no exported member '__assign'. +tests/cases/compiler/external.ts(14,12): error TS2343: This syntax requires an imported helper named '__rest', but module 'tslib' has no exported member '__rest'. -!!! error TS2305: Module 'tslib' has no exported member '__assign'. -!!! error TS2305: Module 'tslib' has no exported member '__decorate'. -!!! error TS2305: Module 'tslib' has no exported member '__extends'. -!!! error TS2305: Module 'tslib' has no exported member '__metadata'. -!!! error TS2305: Module 'tslib' has no exported member '__param'. -!!! error TS2305: Module 'tslib' has no exported member '__rest'. -==== tests/cases/compiler/external.ts (0 errors) ==== +==== tests/cases/compiler/external.ts (6 errors) ==== export class A { } export class B extends A { } + ~~~~~~~~~ +!!! error TS2343: This syntax requires an imported helper named '__extends', but module 'tslib' has no exported member '__extends'. declare var dec: any; @dec + ~~~~ +!!! error TS2343: This syntax requires an imported helper named '__decorate', but module 'tslib' has no exported member '__decorate'. + ~~~~ +!!! error TS2343: This syntax requires an imported helper named '__metadata', but module 'tslib' has no exported member '__metadata'. class C { method(@dec x: number) { + ~~~~ +!!! error TS2343: This syntax requires an imported helper named '__param', but module 'tslib' has no exported member '__param'. } } const o = { a: 1 }; const y = { ...o }; + ~~~~ +!!! error TS2343: This syntax requires an imported helper named '__assign', but module 'tslib' has no exported member '__assign'. const { ...x } = y; + ~ +!!! error TS2343: This syntax requires an imported helper named '__rest', but module 'tslib' has no exported member '__rest'. ==== tests/cases/compiler/script.ts (0 errors) ==== class A { } diff --git a/tests/baselines/reference/importHelpersNoModule.errors.txt b/tests/baselines/reference/importHelpersNoModule.errors.txt index d59fd0537ca5d..b6786ec363552 100644 --- a/tests/baselines/reference/importHelpersNoModule.errors.txt +++ b/tests/baselines/reference/importHelpersNoModule.errors.txt @@ -1,10 +1,11 @@ -error TS2307: Cannot find module 'tslib'. +tests/cases/compiler/external.ts(2,16): error TS2354: This syntax requires an imported helper but module 'tslib' cannot be found. -!!! error TS2307: Cannot find module 'tslib'. -==== tests/cases/compiler/external.ts (0 errors) ==== +==== tests/cases/compiler/external.ts (1 errors) ==== export class A { } export class B extends A { } + ~~~~~~~~~ +!!! error TS2354: This syntax requires an imported helper but module 'tslib' cannot be found. declare var dec: any;