Skip to content

[Transforms] Import external helpers module #9097

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jun 15, 2016
2 changes: 1 addition & 1 deletion src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1765,7 +1765,7 @@ namespace ts {
return bindPropertyOrMethodOrAccessor(<Declaration>node, SymbolFlags.EnumMember, SymbolFlags.EnumMemberExcludes);

case SyntaxKind.JsxSpreadAttribute:
emitFlags |= NodeFlags.HasJsxSpreadAttribute;
emitFlags |= NodeFlags.HasJsxSpreadAttributes;
return;

case SyntaxKind.CallSignature:
Expand Down
80 changes: 70 additions & 10 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1264,10 +1264,13 @@ namespace ts {
}

const moduleReferenceLiteral = <LiteralExpression>moduleReferenceExpression;
return resolveExternalModule(location, moduleReferenceLiteral.text, moduleNotFoundError, moduleReferenceLiteral);
}

function resolveExternalModule(location: Node, moduleReference: string, moduleNotFoundError: DiagnosticMessage, errorNode: Node): Symbol {
// Module names are escaped in our symbol table. However, string literal values aren't.
// Escape the name in the "require(...)" clause to ensure we find the right symbol.
const moduleName = escapeIdentifier(moduleReferenceLiteral.text);
const moduleName = escapeIdentifier(moduleReference);

if (moduleName === undefined) {
return;
Expand All @@ -1282,7 +1285,7 @@ namespace ts {
}
}

const resolvedModule = getResolvedModule(getSourceFileOfNode(location), moduleReferenceLiteral.text);
const resolvedModule = getResolvedModule(getSourceFileOfNode(location), moduleReference);
const sourceFile = resolvedModule && host.getSourceFile(resolvedModule.resolvedFileName);
if (sourceFile) {
if (sourceFile.symbol) {
Expand All @@ -1291,7 +1294,7 @@ namespace ts {
}
if (moduleNotFoundError) {
// report errors only if it was requested
error(moduleReferenceLiteral, Diagnostics.File_0_is_not_a_module, sourceFile.fileName);
error(errorNode, Diagnostics.File_0_is_not_a_module, sourceFile.fileName);
}
return undefined;
}
Expand All @@ -1305,7 +1308,7 @@ namespace ts {

if (moduleNotFoundError) {
// report errors only if it was requested
error(moduleReferenceLiteral, moduleNotFoundError, moduleName);
error(errorNode, moduleNotFoundError, moduleName);
}
return undefined;
}
Expand Down Expand Up @@ -17445,6 +17448,11 @@ namespace ts {

function isReferencedAliasDeclaration(node: Node, checkChildren?: boolean): boolean {
node = getParseTreeNode(node);
// Purely synthesized nodes are always emitted.
if (node === undefined) {
return true;
}

if (isAliasSymbolDeclaration(node)) {
const symbol = getSymbolOfNode(node);
if (symbol && getSymbolLinks(symbol).referenced) {
Expand Down Expand Up @@ -17759,27 +17767,37 @@ namespace ts {

function initializeTypeChecker() {
// Bind all source files and propagate errors
forEach(host.getSourceFiles(), file => {
for (const file of host.getSourceFiles()) {
bindSourceFile(file, compilerOptions);
});
}

let augmentations: LiteralExpression[][];
// Initialize global symbol table
forEach(host.getSourceFiles(), file => {
let augmentations: LiteralExpression[][];
let requestedExternalEmitHelpers: NodeFlags = 0;
let firstFileRequestingExternalHelpers: SourceFile;
for (const file of host.getSourceFiles()) {
if (!isExternalOrCommonJsModule(file)) {
mergeSymbolTable(globals, file.locals);
}
if (file.patternAmbientModules && file.patternAmbientModules.length) {
patternAmbientModules = concatenate(patternAmbientModules, file.patternAmbientModules);
}

if (file.moduleAugmentations.length) {
(augmentations || (augmentations = [])).push(file.moduleAugmentations);
}
if (file.symbol && file.symbol.globalExports) {
mergeSymbolTable(globals, file.symbol.globalExports);
}
});
if ((compilerOptions.isolatedModules || isExternalModule(file)) && !file.isDeclarationFile) {
const fileRequestedExternalEmitHelpers = file.flags & NodeFlags.EmitHelperFlags;
if (fileRequestedExternalEmitHelpers) {
requestedExternalEmitHelpers |= fileRequestedExternalEmitHelpers;
if (firstFileRequestingExternalHelpers === undefined) {
firstFileRequestingExternalHelpers = file;
}
}
}
}

if (augmentations) {
// merge module augmentations.
Expand Down Expand Up @@ -17842,6 +17860,48 @@ namespace ts {
const symbol = getGlobalSymbol("ReadonlyArray", SymbolFlags.Type, /*diagnostic*/ undefined);
globalReadonlyArrayType = symbol && <GenericType>getTypeOfGlobalSymbol(symbol, /*arity*/ 1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider returning after the first error, to avoid reporting the same error for every file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to traverse each file to determine which helpers I need to verify. I can possibly add some additional short-circuiting logic, but nothing as simple as "bail on first error".

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.ES6) {
verifyHelperSymbol(exports, "__extends", SymbolFlags.Value);
}
if (requestedExternalEmitHelpers & NodeFlags.HasJsxSpreadAttributes && compilerOptions.jsx !== JsxEmit.Preserve) {
verifyHelperSymbol(exports, "__assign", 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);
}
}
}
}

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 createInstantiatedPromiseLikeType(): ObjectType {
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,11 @@ namespace ts {
name: "strictNullChecks",
type: "boolean",
description: Diagnostics.Enable_strict_null_checks
},
{
name: "importHelpers",
type: "boolean",
description: Diagnostics.Import_emit_helpers_from_tslib
}
];

Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2776,6 +2776,10 @@
"category": "Message",
"code": 6132
},
"Import emit helpers from 'tslib'.": {
"category": "Message",
"code": 6133
},

"Variable '{0}' implicitly has an '{1}' type.": {
"category": "Error",
Expand Down
76 changes: 42 additions & 34 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2106,48 +2106,56 @@ const _super = (function (geti, seti) {
}

function emitEmitHelpers(node: SourceFile) {
let helpersEmitted = false;

// Only emit helpers if the user did not say otherwise.
if (!compilerOptions.noEmitHelpers) {
// Only Emit __extends function when target ES5.
// For target ES6 and above, we can emit classDeclaration as is.
if ((languageVersion < ScriptTarget.ES6) && (!extendsEmitted && node.flags & NodeFlags.HasClassExtends)) {
writeLines(extendsHelper);
extendsEmitted = true;
helpersEmitted = true;
}
if (compilerOptions.noEmitHelpers) {
return false;
}

if (compilerOptions.jsx !== JsxEmit.Preserve && !assignEmitted && (node.flags & NodeFlags.HasJsxSpreadAttribute)) {
writeLines(assignHelper);
assignEmitted = true;
}
// Don't emit helpers if we can import them.
if (compilerOptions.importHelpers
&& (isExternalModule(node) || compilerOptions.isolatedModules)) {
return false;
}

if (!decorateEmitted && node.flags & NodeFlags.HasDecorators) {
writeLines(decorateHelper);
if (compilerOptions.emitDecoratorMetadata) {
writeLines(metadataHelper);
}
let helpersEmitted = false;

decorateEmitted = true;
helpersEmitted = true;
}
// Only Emit __extends function when target ES5.
// For target ES6 and above, we can emit classDeclaration as is.
if ((languageVersion < ScriptTarget.ES6) && (!extendsEmitted && node.flags & NodeFlags.HasClassExtends)) {
writeLines(extendsHelper);
extendsEmitted = true;
helpersEmitted = true;
}

if (!paramEmitted && node.flags & NodeFlags.HasParamDecorators) {
writeLines(paramHelper);
paramEmitted = true;
helpersEmitted = true;
}
if (compilerOptions.jsx !== JsxEmit.Preserve && !assignEmitted && (node.flags & NodeFlags.HasJsxSpreadAttributes)) {
writeLines(assignHelper);
assignEmitted = true;
}

if (!awaiterEmitted && node.flags & NodeFlags.HasAsyncFunctions) {
writeLines(awaiterHelper);
awaiterEmitted = true;
helpersEmitted = true;
if (!decorateEmitted && node.flags & NodeFlags.HasDecorators) {
writeLines(decorateHelper);
if (compilerOptions.emitDecoratorMetadata) {
writeLines(metadataHelper);
}

if (helpersEmitted) {
writeLine();
}
decorateEmitted = true;
helpersEmitted = true;
}

if (!paramEmitted && node.flags & NodeFlags.HasParamDecorators) {
writeLines(paramHelper);
paramEmitted = true;
helpersEmitted = true;
}

if (!awaiterEmitted && node.flags & NodeFlags.HasAsyncFunctions) {
writeLines(awaiterHelper);
awaiterEmitted = true;
helpersEmitted = true;
}

if (helpersEmitted) {
writeLine();
}

return helpersEmitted;
Expand Down
Loading