Improve diagnostic messages for imported helpers

This commit is contained in:
Ron Buckton 2016-11-16 11:54:02 -08:00
parent 3a95f9244b
commit d775f0f569
7 changed files with 133 additions and 108 deletions

View file

@ -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((<FunctionLikeDeclaration>node).body)) {
node.flags |= NodeFlags.HasImplicitReturn;
@ -1950,9 +1949,6 @@ namespace ts {
return bindParameter(<ParameterDeclaration>node);
case SyntaxKind.VariableDeclaration:
case SyntaxKind.BindingElement:
if ((node as BindingElement).dotDotDotToken && node.parent.kind === SyntaxKind.ObjectBindingPattern) {
emitFlags |= NodeFlags.HasRestAttribute;
}
return bindVariableDeclarationOrBindingElement(<VariableDeclaration | BindingElement>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)) {

View file

@ -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();
@ -11272,6 +11274,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 = [];
@ -11459,6 +11464,9 @@ namespace ts {
}
function checkJsxSpreadAttribute(node: JsxSpreadAttribute, elementAttributesType: Type, nameTable: Map<boolean>) {
if (compilerOptions.jsx === JsxEmit.React) {
checkExternalEmitHelpers(node, ExternalEmitHelpers.Assign);
}
const type = checkExpression(node.expression);
const props = getPropertiesOfType(type);
for (const prop of props) {
@ -14223,6 +14231,9 @@ namespace ts {
}
}
else if (property.kind === SyntaxKind.SpreadAssignment) {
if (languageVersion < ScriptTarget.ESNext) {
checkExternalEmitHelpers(property, ExternalEmitHelpers.Rest);
}
checkReferenceExpression(property.expression, Diagnostics.The_target_of_an_object_rest_assignment_must_be_a_variable_or_a_property_access);
}
else {
@ -15107,6 +15118,13 @@ namespace ts {
checkGrammarFunctionLikeDeclaration(<FunctionLikeDeclaration>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);
@ -16242,7 +16260,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:
@ -16629,7 +16655,7 @@ namespace ts {
}
function checkCollisionWithGlobalPromiseInGeneratedCode(node: Node, name: Identifier): void {
if (!needCollisionCheckForIdentifier(node, name, "Promise")) {
if (languageVersion >= ScriptTarget.ES2017 || !needCollisionCheckForIdentifier(node, name, "Promise")) {
return;
}
@ -16806,6 +16832,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(<ComputedPropertyName>node.propertyName);
@ -17724,6 +17753,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];
@ -20164,8 +20197,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);
@ -20185,15 +20216,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) {
@ -20259,57 +20281,51 @@ namespace ts {
const symbol = getGlobalSymbol("ReadonlyArray", SymbolFlags.Type, /*diagnostic*/ undefined);
globalReadonlyArrayType = symbol && <GenericType>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) {

View file

@ -1023,6 +1023,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
@ -1063,6 +1067,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

View file

@ -417,26 +417,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,
@ -3687,6 +3681,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

View file

@ -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:

View file

@ -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 { }

View file

@ -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;