Merge branch 'master' of https://github.com/Microsoft/TypeScript into bug/24542-bad-error-message-for-import-ing-an-export

This commit is contained in:
Alexander T
2018-10-18 11:49:58 +03:00
1907 changed files with 86633 additions and 47584 deletions
+91 -83
View File
@@ -234,13 +234,17 @@ namespace ts {
}
if (symbolFlags & SymbolFlags.Value) {
const { valueDeclaration } = symbol;
if (!valueDeclaration ||
(isAssignmentDeclaration(valueDeclaration) && !isAssignmentDeclaration(node)) ||
(valueDeclaration.kind !== node.kind && isEffectiveModuleDeclaration(valueDeclaration))) {
// other kinds of value declarations take precedence over modules and assignment declarations
symbol.valueDeclaration = node;
}
setValueDeclaration(symbol, node);
}
}
function setValueDeclaration(symbol: Symbol, node: Declaration): void {
const { valueDeclaration } = symbol;
if (!valueDeclaration ||
(isAssignmentDeclaration(valueDeclaration) && !isAssignmentDeclaration(node)) ||
(valueDeclaration.kind !== node.kind && isEffectiveModuleDeclaration(valueDeclaration))) {
// other kinds of value declarations take precedence over modules and assignment declarations
symbol.valueDeclaration = node;
}
}
@@ -288,7 +292,7 @@ namespace ts {
// module.exports = ...
return InternalSymbolName.ExportEquals;
case SyntaxKind.BinaryExpression:
if (getSpecialPropertyAssignmentKind(node as BinaryExpression) === SpecialPropertyAssignmentKind.ModuleExports) {
if (getAssignmentDeclarationKind(node as BinaryExpression) === AssignmentDeclarationKind.ModuleExports) {
// module.exports = ...
return InternalSymbolName.ExportEquals;
}
@@ -374,8 +378,8 @@ namespace ts {
// prototype symbols like methods.
symbolTable.set(name, symbol = createSymbol(SymbolFlags.None, name));
}
else if (!(includes & SymbolFlags.Variable && symbol.flags & SymbolFlags.JSContainer)) {
// JSContainers are allowed to merge with variables, no matter what other flags they have.
else if (!(includes & SymbolFlags.Variable && symbol.flags & SymbolFlags.Assignment)) {
// Assignment declarations are allowed to merge with variables, no matter what other flags they have.
if (isNamedDeclaration(node)) {
node.name.parent = node;
}
@@ -461,7 +465,7 @@ namespace ts {
// during global merging in the checker. Why? The only case when ambient module is permitted inside another module is module augmentation
// and this case is specially handled. Module augmentations should only be merged with original module definition
// and should never be merged directly with other augmentation, and the latter case would be possible if automatic merge is allowed.
if (isJSDocTypeAlias(node)) Debug.assert(isInJavaScriptFile(node)); // We shouldn't add symbols for JSDoc nodes if not in a JS file.
if (isJSDocTypeAlias(node)) Debug.assert(isInJSFile(node)); // We shouldn't add symbols for JSDoc nodes if not in a JS file.
if ((!isAmbientModule(node) && (hasExportModifier || container.flags & NodeFlags.ExportContext)) || isJSDocTypeAlias(node)) {
if (hasModifier(node, ModifierFlags.Default) && !getDeclarationName(node)) {
return declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); // No local symbol for an unnamed default!
@@ -636,6 +640,7 @@ namespace ts {
function bindChildrenWorker(node: Node): void {
if (checkUnreachable(node)) {
bindEachChild(node);
bindJSDoc(node);
return;
}
switch (node.kind) {
@@ -1176,7 +1181,6 @@ namespace ts {
}
const preCaseLabel = createBranchLabel();
addAntecedent(preCaseLabel, createFlowSwitchClause(preSwitchCaseFlow!, node.parent, clauseStart, i + 1));
addAntecedent(preCaseLabel, createFlowSwitchClause(preSwitchCaseFlow!, node.parent, clauseStart, i + 1));
addAntecedent(preCaseLabel, fallthroughFlow);
currentFlow = finishFlowLabel(preCaseLabel);
const clause = clauses[i];
@@ -2009,7 +2013,7 @@ namespace ts {
function bindJSDoc(node: Node) {
if (hasJSDocNodes(node)) {
if (isInJavaScriptFile(node)) {
if (isInJSFile(node)) {
for (const j of node.jsDoc!) {
bind(j);
}
@@ -2075,7 +2079,7 @@ namespace ts {
if (isSpecialPropertyDeclaration(node as PropertyAccessExpression)) {
bindSpecialPropertyDeclaration(node as PropertyAccessExpression);
}
if (isInJavaScriptFile(node) &&
if (isInJSFile(node) &&
file.commonJsModuleIndicator &&
isModuleExportsPropertyAccessExpression(node as PropertyAccessExpression) &&
!lookupSymbolForNameWorker(blockScopeContainer, "module" as __String)) {
@@ -2084,27 +2088,27 @@ namespace ts {
}
break;
case SyntaxKind.BinaryExpression:
const specialKind = getSpecialPropertyAssignmentKind(node as BinaryExpression);
const specialKind = getAssignmentDeclarationKind(node as BinaryExpression);
switch (specialKind) {
case SpecialPropertyAssignmentKind.ExportsProperty:
case AssignmentDeclarationKind.ExportsProperty:
bindExportsPropertyAssignment(node as BinaryExpression);
break;
case SpecialPropertyAssignmentKind.ModuleExports:
case AssignmentDeclarationKind.ModuleExports:
bindModuleExportsAssignment(node as BinaryExpression);
break;
case SpecialPropertyAssignmentKind.PrototypeProperty:
case AssignmentDeclarationKind.PrototypeProperty:
bindPrototypePropertyAssignment((node as BinaryExpression).left as PropertyAccessEntityNameExpression, node);
break;
case SpecialPropertyAssignmentKind.Prototype:
case AssignmentDeclarationKind.Prototype:
bindPrototypeAssignment(node as BinaryExpression);
break;
case SpecialPropertyAssignmentKind.ThisProperty:
case AssignmentDeclarationKind.ThisProperty:
bindThisPropertyAssignment(node as BinaryExpression);
break;
case SpecialPropertyAssignmentKind.Property:
case AssignmentDeclarationKind.Property:
bindSpecialPropertyAssignment(node as BinaryExpression);
break;
case SpecialPropertyAssignmentKind.None:
case AssignmentDeclarationKind.None:
// Nothing to do
break;
default:
@@ -2184,7 +2188,7 @@ namespace ts {
return bindFunctionExpression(<FunctionExpression>node);
case SyntaxKind.CallExpression:
if (isInJavaScriptFile(node)) {
if (isInJSFile(node)) {
bindCallExpression(<CallExpression>node);
}
break;
@@ -2286,14 +2290,19 @@ namespace ts {
bindAnonymousDeclaration(node, SymbolFlags.Alias, getDeclarationName(node)!);
}
else {
const flags = node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(node)
const flags = exportAssignmentIsAlias(node)
// An export default clause with an EntityNameExpression or a class expression exports all meanings of that identifier or expression;
? SymbolFlags.Alias
// An export default clause with any other expression exports a value
: SymbolFlags.Property;
// If there is an `export default x;` alias declaration, can't `export default` anything else.
// (In contrast, you can still have `export default function f() {}` and `export default interface I {}`.)
declareSymbol(container.symbol.exports, container.symbol, node, flags, SymbolFlags.All);
const symbol = declareSymbol(container.symbol.exports, container.symbol, node, flags, SymbolFlags.All);
if (node.isExportEquals) {
// Will be an error later, since the module already has other exports. Just make sure this has a valueDeclaration set.
setValueDeclaration(symbol, node);
}
}
}
@@ -2301,27 +2310,17 @@ namespace ts {
if (node.modifiers && node.modifiers.length) {
file.bindDiagnostics.push(createDiagnosticForNode(node, Diagnostics.Modifiers_cannot_appear_here));
}
if (node.parent.kind !== SyntaxKind.SourceFile) {
file.bindDiagnostics.push(createDiagnosticForNode(node, Diagnostics.Global_module_exports_may_only_appear_at_top_level));
return;
const diag = !isSourceFile(node.parent) ? Diagnostics.Global_module_exports_may_only_appear_at_top_level
: !isExternalModule(node.parent) ? Diagnostics.Global_module_exports_may_only_appear_in_module_files
: !node.parent.isDeclarationFile ? Diagnostics.Global_module_exports_may_only_appear_in_declaration_files
: undefined;
if (diag) {
file.bindDiagnostics.push(createDiagnosticForNode(node, diag));
}
else {
const parent = node.parent as SourceFile;
if (!isExternalModule(parent)) {
file.bindDiagnostics.push(createDiagnosticForNode(node, Diagnostics.Global_module_exports_may_only_appear_in_module_files));
return;
}
if (!parent.isDeclarationFile) {
file.bindDiagnostics.push(createDiagnosticForNode(node, Diagnostics.Global_module_exports_may_only_appear_in_declaration_files));
return;
}
file.symbol.globalExports = file.symbol.globalExports || createSymbolTable();
declareSymbol(file.symbol.globalExports, file.symbol, node, SymbolFlags.Alias, SymbolFlags.AliasExcludes);
}
file.symbol.globalExports = file.symbol.globalExports || createSymbolTable();
declareSymbol(file.symbol.globalExports, file.symbol, node, SymbolFlags.Alias, SymbolFlags.AliasExcludes);
}
function bindExportDeclaration(node: ExportDeclaration) {
@@ -2361,7 +2360,7 @@ namespace ts {
const lhs = node.left as PropertyAccessEntityNameExpression;
const symbol = forEachIdentifierInEntityName(lhs.expression, /*parent*/ undefined, (id, symbol) => {
if (symbol) {
addDeclarationToSymbol(symbol, id, SymbolFlags.Module | SymbolFlags.JSContainer);
addDeclarationToSymbol(symbol, id, SymbolFlags.Module | SymbolFlags.Assignment);
}
return symbol;
});
@@ -2394,7 +2393,7 @@ namespace ts {
}
function bindThisPropertyAssignment(node: BinaryExpression | PropertyAccessExpression) {
Debug.assert(isInJavaScriptFile(node));
Debug.assert(isInJSFile(node));
const thisContainer = getThisContainer(node, /*includeArrowFunctions*/ false);
switch (thisContainer.kind) {
case SyntaxKind.FunctionDeclaration:
@@ -2456,7 +2455,7 @@ namespace ts {
node.left.parent = node;
node.right.parent = node;
const lhs = node.left as PropertyAccessEntityNameExpression;
bindPropertyAssignment(lhs, lhs, /*isPrototypeProperty*/ false);
bindPropertyAssignment(lhs.expression, lhs, /*isPrototypeProperty*/ false);
}
/**
@@ -2482,7 +2481,7 @@ namespace ts {
const lhs = node.left as PropertyAccessEntityNameExpression;
// Class declarations in Typescript do not allow property declarations
const parentSymbol = lookupSymbolForPropertyAccess(lhs.expression);
if (!isInJavaScriptFile(node) && !isFunctionSymbol(parentSymbol)) {
if (!isInJSFile(node) && !isFunctionSymbol(parentSymbol)) {
return;
}
// Fix up parent pointers since we're going to use these nodes before we bind into them
@@ -2513,21 +2512,23 @@ namespace ts {
const isToplevel = isBinaryExpression(propertyAccess.parent)
? getParentOfBinaryExpression(propertyAccess.parent).parent.kind === SyntaxKind.SourceFile
: propertyAccess.parent.parent.kind === SyntaxKind.SourceFile;
if (!isPrototypeProperty && (!namespaceSymbol || !(namespaceSymbol.flags & SymbolFlags.Namespace)) && isToplevel) {
if (isToplevel && !isPrototypeProperty && (!namespaceSymbol || !(namespaceSymbol.flags & SymbolFlags.Namespace))) {
// make symbols or add declarations for intermediate containers
const flags = SymbolFlags.Module | SymbolFlags.JSContainer;
const excludeFlags = SymbolFlags.ValueModuleExcludes & ~SymbolFlags.JSContainer;
const flags = SymbolFlags.Module | SymbolFlags.Assignment;
const excludeFlags = SymbolFlags.ValueModuleExcludes & ~SymbolFlags.Assignment;
namespaceSymbol = forEachIdentifierInEntityName(propertyAccess.expression, namespaceSymbol, (id, symbol, parent) => {
if (symbol) {
addDeclarationToSymbol(symbol, id, flags);
return symbol;
}
else {
return declareSymbol(parent ? parent.exports! : container.locals!, parent, id, flags, excludeFlags);
const table = parent ? parent.exports! :
file.jsGlobalAugmentations || (file.jsGlobalAugmentations = createSymbolTable());
return declareSymbol(table, parent, id, flags, excludeFlags);
}
});
}
if (!namespaceSymbol || !isJavascriptContainer(namespaceSymbol)) {
if (!namespaceSymbol || !isExpandoSymbol(namespaceSymbol)) {
return;
}
@@ -2536,14 +2537,14 @@ namespace ts {
(namespaceSymbol.members || (namespaceSymbol.members = createSymbolTable())) :
(namespaceSymbol.exports || (namespaceSymbol.exports = createSymbolTable()));
const isMethod = isFunctionLikeDeclaration(getAssignedJavascriptInitializer(propertyAccess)!);
const isMethod = isFunctionLikeDeclaration(getAssignedExpandoInitializer(propertyAccess)!);
const includes = isMethod ? SymbolFlags.Method : SymbolFlags.Property;
const excludes = isMethod ? SymbolFlags.MethodExcludes : SymbolFlags.PropertyExcludes;
declareSymbol(symbolTable, namespaceSymbol, propertyAccess, includes | SymbolFlags.JSContainer, excludes & ~SymbolFlags.JSContainer);
declareSymbol(symbolTable, namespaceSymbol, propertyAccess, includes | SymbolFlags.Assignment, excludes & ~SymbolFlags.Assignment);
}
/**
* Javascript containers are:
* Javascript expando values are:
* - Functions
* - classes
* - namespaces
@@ -2552,7 +2553,7 @@ namespace ts {
* - with empty object literals
* - with non-empty object literals if assigned to the prototype property
*/
function isJavascriptContainer(symbol: Symbol): boolean {
function isExpandoSymbol(symbol: Symbol): boolean {
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Class | SymbolFlags.NamespaceModule)) {
return true;
}
@@ -2565,7 +2566,7 @@ namespace ts {
init = init && getRightMostAssignedExpression(init);
if (init) {
const isPrototypeAssignment = isPrototypeAccess(isVariableDeclaration(node) ? node.name : isBinaryExpression(node) ? node.left : node);
return !!getJavascriptInitializer(isBinaryExpression(init) && init.operatorToken.kind === SyntaxKind.BarBarToken ? init.right : init, isPrototypeAssignment);
return !!getExpandoInitializer(isBinaryExpression(init) && init.operatorToken.kind === SyntaxKind.BarBarToken ? init.right : init, isPrototypeAssignment);
}
return false;
}
@@ -2657,7 +2658,7 @@ namespace ts {
}
if (!isBindingPattern(node.name)) {
const isEnum = !!getJSDocEnumTag(node);
const isEnum = isInJSFile(node) && !!getJSDocEnumTag(node);
const enumFlags = (isEnum ? SymbolFlags.RegularEnum : SymbolFlags.None);
const enumExcludes = (isEnum ? SymbolFlags.RegularEnumExcludes : SymbolFlags.None);
if (isBlockOrCatchScoped(node)) {
@@ -2892,6 +2893,9 @@ namespace ts {
if (local) {
return local.exportSymbol || local;
}
if (isSourceFile(container) && container.jsGlobalAugmentations && container.jsGlobalAugmentations.has(name)) {
return container.jsGlobalAugmentations.get(name);
}
return container.symbol && container.symbol.exports && container.symbol.exports.get(name);
}
@@ -2996,7 +3000,7 @@ namespace ts {
transformFlags |= TransformFlags.AssertTypeScript;
}
if (subtreeFlags & TransformFlags.ContainsSpread
if (subtreeFlags & TransformFlags.ContainsRestOrSpread
|| (expression.transformFlags & (TransformFlags.Super | TransformFlags.ContainsSuper))) {
// If the this node contains a SpreadExpression, or is a super call, then it is an ES6
// node.
@@ -3027,7 +3031,7 @@ namespace ts {
if (node.typeArguments) {
transformFlags |= TransformFlags.AssertTypeScript;
}
if (subtreeFlags & TransformFlags.ContainsSpread) {
if (subtreeFlags & TransformFlags.ContainsRestOrSpread) {
// If the this node contains a SpreadElementExpression then it is an ES6
// node.
transformFlags |= TransformFlags.AssertES2015;
@@ -3070,18 +3074,18 @@ namespace ts {
// syntax.
if (node.questionToken
|| node.type
|| subtreeFlags & TransformFlags.ContainsDecorators
|| (subtreeFlags & TransformFlags.ContainsTypeScriptClassSyntax && some(node.decorators))
|| isThisIdentifier(name)) {
transformFlags |= TransformFlags.AssertTypeScript;
}
// If a parameter has an accessibility modifier, then it is TypeScript syntax.
if (hasModifier(node, ModifierFlags.ParameterPropertyModifier)) {
transformFlags |= TransformFlags.AssertTypeScript | TransformFlags.ContainsParameterPropertyAssignments;
transformFlags |= TransformFlags.AssertTypeScript | TransformFlags.ContainsTypeScriptClassSyntax;
}
// parameters with object rest destructuring are ES Next syntax
if (subtreeFlags & TransformFlags.ContainsObjectRest) {
if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) {
transformFlags |= TransformFlags.AssertESNext;
}
@@ -3134,7 +3138,7 @@ namespace ts {
// TypeScript syntax.
// An exported declaration may be TypeScript syntax, but is handled by the visitor
// for a namespace declaration.
if ((subtreeFlags & TransformFlags.TypeScriptClassSyntaxMask)
if ((subtreeFlags & TransformFlags.ContainsTypeScriptClassSyntax)
|| node.typeParameters) {
transformFlags |= TransformFlags.AssertTypeScript;
}
@@ -3156,7 +3160,7 @@ namespace ts {
// A class with a parameter property assignment, property initializer, or decorator is
// TypeScript syntax.
if (subtreeFlags & TransformFlags.TypeScriptClassSyntaxMask
if (subtreeFlags & TransformFlags.ContainsTypeScriptClassSyntax
|| node.typeParameters) {
transformFlags |= TransformFlags.AssertTypeScript;
}
@@ -3233,7 +3237,7 @@ namespace ts {
}
// function declarations with object rest destructuring are ES Next syntax
if (subtreeFlags & TransformFlags.ContainsObjectRest) {
if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) {
transformFlags |= TransformFlags.AssertESNext;
}
@@ -3257,7 +3261,7 @@ namespace ts {
}
// function declarations with object rest destructuring are ES Next syntax
if (subtreeFlags & TransformFlags.ContainsObjectRest) {
if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) {
transformFlags |= TransformFlags.AssertESNext;
}
@@ -3288,7 +3292,7 @@ namespace ts {
}
// function declarations with object rest destructuring are ES Next syntax
if (subtreeFlags & TransformFlags.ContainsObjectRest) {
if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) {
transformFlags |= TransformFlags.AssertESNext;
}
@@ -3303,7 +3307,7 @@ namespace ts {
// If the PropertyDeclaration has an initializer or a computed name, we need to inform its ancestor
// so that it handle the transformation.
if (node.initializer || isComputedPropertyName(node.name)) {
transformFlags |= TransformFlags.ContainsPropertyInitializer;
transformFlags |= TransformFlags.ContainsTypeScriptClassSyntax;
}
node.transformFlags = transformFlags | TransformFlags.HasComputedFlags;
@@ -3337,7 +3341,7 @@ namespace ts {
}
// function declarations with object rest destructuring are ES Next syntax
if (subtreeFlags & TransformFlags.ContainsObjectRest) {
if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) {
transformFlags |= TransformFlags.AssertESNext;
}
@@ -3379,7 +3383,7 @@ namespace ts {
}
// function expressions with object rest destructuring are ES Next syntax
if (subtreeFlags & TransformFlags.ContainsObjectRest) {
if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) {
transformFlags |= TransformFlags.AssertESNext;
}
@@ -3420,7 +3424,7 @@ namespace ts {
}
// arrow functions with object rest destructuring are ES Next syntax
if (subtreeFlags & TransformFlags.ContainsObjectRest) {
if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) {
transformFlags |= TransformFlags.AssertESNext;
}
@@ -3440,7 +3444,9 @@ namespace ts {
// ES6 syntax, and requires a lexical `this` binding.
if (transformFlags & TransformFlags.Super) {
transformFlags ^= TransformFlags.Super;
transformFlags |= TransformFlags.ContainsSuper;
// super inside of an async function requires hoisting the super access (ES2017).
// same for super inside of an async generator, which is ESNext.
transformFlags |= TransformFlags.ContainsSuper | TransformFlags.ContainsES2017 | TransformFlags.ContainsESNext;
}
node.transformFlags = transformFlags | TransformFlags.HasComputedFlags;
@@ -3456,7 +3462,9 @@ namespace ts {
// ES6 syntax, and requires a lexical `this` binding.
if (expressionFlags & TransformFlags.Super) {
transformFlags &= ~TransformFlags.Super;
transformFlags |= TransformFlags.ContainsSuper;
// super inside of an async function requires hoisting the super access (ES2017).
// same for super inside of an async generator, which is ESNext.
transformFlags |= TransformFlags.ContainsSuper | TransformFlags.ContainsES2017 | TransformFlags.ContainsESNext;
}
node.transformFlags = transformFlags | TransformFlags.HasComputedFlags;
@@ -3468,7 +3476,7 @@ namespace ts {
transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsBindingPattern;
// A VariableDeclaration containing ObjectRest is ESNext syntax
if (subtreeFlags & TransformFlags.ContainsObjectRest) {
if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) {
transformFlags |= TransformFlags.AssertESNext;
}
@@ -3717,11 +3725,11 @@ namespace ts {
break;
case SyntaxKind.SpreadElement:
transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsSpread;
transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsRestOrSpread;
break;
case SyntaxKind.SpreadAssignment:
transformFlags |= TransformFlags.AssertESNext | TransformFlags.ContainsObjectSpread;
transformFlags |= TransformFlags.AssertESNext | TransformFlags.ContainsObjectRestOrSpread;
break;
case SyntaxKind.SuperKeyword:
@@ -3737,8 +3745,8 @@ namespace ts {
case SyntaxKind.ObjectBindingPattern:
transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsBindingPattern;
if (subtreeFlags & TransformFlags.ContainsRest) {
transformFlags |= TransformFlags.AssertESNext | TransformFlags.ContainsObjectRest;
if (subtreeFlags & TransformFlags.ContainsRestOrSpread) {
transformFlags |= TransformFlags.AssertESNext | TransformFlags.ContainsObjectRestOrSpread;
}
excludeFlags = TransformFlags.BindingPatternExcludes;
break;
@@ -3751,13 +3759,13 @@ namespace ts {
case SyntaxKind.BindingElement:
transformFlags |= TransformFlags.AssertES2015;
if ((<BindingElement>node).dotDotDotToken) {
transformFlags |= TransformFlags.ContainsRest;
transformFlags |= TransformFlags.ContainsRestOrSpread;
}
break;
case SyntaxKind.Decorator:
// This node is TypeScript syntax, and marks its container as also being TypeScript syntax.
transformFlags |= TransformFlags.AssertTypeScript | TransformFlags.ContainsDecorators;
transformFlags |= TransformFlags.AssertTypeScript | TransformFlags.ContainsTypeScriptClassSyntax;
break;
case SyntaxKind.ObjectLiteralExpression:
@@ -3774,7 +3782,7 @@ namespace ts {
transformFlags |= TransformFlags.ContainsLexicalThis;
}
if (subtreeFlags & TransformFlags.ContainsObjectSpread) {
if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) {
// If an ObjectLiteralExpression contains a spread element, then it
// is an ES next node.
transformFlags |= TransformFlags.AssertESNext;
@@ -3785,7 +3793,7 @@ namespace ts {
case SyntaxKind.ArrayLiteralExpression:
case SyntaxKind.NewExpression:
excludeFlags = TransformFlags.ArrayLiteralOrCallOrNewExcludes;
if (subtreeFlags & TransformFlags.ContainsSpread) {
if (subtreeFlags & TransformFlags.ContainsRestOrSpread) {
// If the this node contains a SpreadExpression, then it is an ES6
// node.
transformFlags |= TransformFlags.AssertES2015;
+18 -11
View File
@@ -294,7 +294,7 @@ namespace ts {
configFileParsingDiagnostics: ReadonlyArray<Diagnostic>;
}
export function getBuilderCreationParameters(newProgramOrRootNames: Program | ReadonlyArray<string> | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: BuilderProgram | CompilerHost, configFileParsingDiagnosticsOrOldProgram?: ReadonlyArray<Diagnostic> | BuilderProgram, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>): BuilderCreationParameters {
export function getBuilderCreationParameters(newProgramOrRootNames: Program | ReadonlyArray<string> | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: BuilderProgram | CompilerHost, configFileParsingDiagnosticsOrOldProgram?: ReadonlyArray<Diagnostic> | BuilderProgram, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>, projectReferences?: ReadonlyArray<ProjectReference>): BuilderCreationParameters {
let host: BuilderProgramHost;
let newProgram: Program;
let oldProgram: BuilderProgram;
@@ -307,7 +307,14 @@ namespace ts {
}
else if (isArray(newProgramOrRootNames)) {
oldProgram = configFileParsingDiagnosticsOrOldProgram as BuilderProgram;
newProgram = createProgram(newProgramOrRootNames, hostOrOptions as CompilerOptions, oldProgramOrHost as CompilerHost, oldProgram && oldProgram.getProgram(), configFileParsingDiagnostics);
newProgram = createProgram({
rootNames: newProgramOrRootNames,
options: hostOrOptions as CompilerOptions,
host: oldProgramOrHost as CompilerHost,
oldProgram: oldProgram && oldProgram.getProgram(),
configFileParsingDiagnostics,
projectReferences
});
host = oldProgramOrHost as CompilerHost;
}
else {
@@ -623,9 +630,9 @@ namespace ts {
* Create the builder to manage semantic diagnostics and cache them
*/
export function createSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>): SemanticDiagnosticsBuilderProgram;
export function createSemanticDiagnosticsBuilderProgram(rootNames: ReadonlyArray<string> | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>): SemanticDiagnosticsBuilderProgram;
export function createSemanticDiagnosticsBuilderProgram(newProgramOrRootNames: Program | ReadonlyArray<string> | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | SemanticDiagnosticsBuilderProgram, configFileParsingDiagnosticsOrOldProgram?: ReadonlyArray<Diagnostic> | SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>) {
return createBuilderProgram(BuilderProgramKind.SemanticDiagnosticsBuilderProgram, getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics));
export function createSemanticDiagnosticsBuilderProgram(rootNames: ReadonlyArray<string> | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>, projectReferences?: ReadonlyArray<ProjectReference>): SemanticDiagnosticsBuilderProgram;
export function createSemanticDiagnosticsBuilderProgram(newProgramOrRootNames: Program | ReadonlyArray<string> | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | SemanticDiagnosticsBuilderProgram, configFileParsingDiagnosticsOrOldProgram?: ReadonlyArray<Diagnostic> | SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>, projectReferences?: ReadonlyArray<ProjectReference>) {
return createBuilderProgram(BuilderProgramKind.SemanticDiagnosticsBuilderProgram, getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences));
}
/**
@@ -633,18 +640,18 @@ namespace ts {
* to emit the those files and manage semantic diagnostics cache as well
*/
export function createEmitAndSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>): EmitAndSemanticDiagnosticsBuilderProgram;
export function createEmitAndSemanticDiagnosticsBuilderProgram(rootNames: ReadonlyArray<string> | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>): EmitAndSemanticDiagnosticsBuilderProgram;
export function createEmitAndSemanticDiagnosticsBuilderProgram(newProgramOrRootNames: Program | ReadonlyArray<string> | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnosticsOrOldProgram?: ReadonlyArray<Diagnostic> | EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>) {
return createBuilderProgram(BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram, getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics));
export function createEmitAndSemanticDiagnosticsBuilderProgram(rootNames: ReadonlyArray<string> | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>, projectReferences?: ReadonlyArray<ProjectReference>): EmitAndSemanticDiagnosticsBuilderProgram;
export function createEmitAndSemanticDiagnosticsBuilderProgram(newProgramOrRootNames: Program | ReadonlyArray<string> | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnosticsOrOldProgram?: ReadonlyArray<Diagnostic> | EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>, projectReferences?: ReadonlyArray<ProjectReference>) {
return createBuilderProgram(BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram, getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences));
}
/**
* Creates a builder thats just abstraction over program and can be used with watch
*/
export function createAbstractBuilder(newProgram: Program, host: BuilderProgramHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>): BuilderProgram;
export function createAbstractBuilder(rootNames: ReadonlyArray<string> | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>): BuilderProgram;
export function createAbstractBuilder(newProgramOrRootNames: Program | ReadonlyArray<string> | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | BuilderProgram, configFileParsingDiagnosticsOrOldProgram?: ReadonlyArray<Diagnostic> | BuilderProgram, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>): BuilderProgram {
const { newProgram: program } = getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics);
export function createAbstractBuilder(rootNames: ReadonlyArray<string> | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>, projectReferences?: ReadonlyArray<ProjectReference>): BuilderProgram;
export function createAbstractBuilder(newProgramOrRootNames: Program | ReadonlyArray<string> | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | BuilderProgram, configFileParsingDiagnosticsOrOldProgram?: ReadonlyArray<Diagnostic> | BuilderProgram, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>, projectReferences?: ReadonlyArray<ProjectReference>): BuilderProgram {
const { newProgram: program } = getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences);
return {
// Only return program, all other methods are not implemented
getProgram: () => program,
+41 -3
View File
@@ -88,7 +88,7 @@ namespace ts.BuilderState {
function getReferencedFileFromImportedModuleSymbol(symbol: Symbol) {
if (symbol.declarations && symbol.declarations[0]) {
const declarationSourceFile = getSourceFileOfNode(symbol.declarations[0]);
return declarationSourceFile && declarationSourceFile.path;
return declarationSourceFile && declarationSourceFile.resolvedPath;
}
}
@@ -100,6 +100,13 @@ namespace ts.BuilderState {
return symbol && getReferencedFileFromImportedModuleSymbol(symbol);
}
/**
* Gets the path to reference file from file name, it could be resolvedPath if present otherwise path
*/
function getReferencedFileFromFileName(program: Program, fileName: string, sourceFileDirectory: Path, getCanonicalFileName: GetCanonicalFileName): Path {
return toPath(program.getProjectReferenceRedirect(fileName) || fileName, sourceFileDirectory, getCanonicalFileName);
}
/**
* Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true
*/
@@ -123,7 +130,7 @@ namespace ts.BuilderState {
// Handle triple slash references
if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) {
for (const referencedFile of sourceFile.referencedFiles) {
const referencedPath = toPath(referencedFile.fileName, sourceFileDirectory, getCanonicalFileName);
const referencedPath = getReferencedFileFromFileName(program, referencedFile.fileName, sourceFileDirectory, getCanonicalFileName);
addReferencedFile(referencedPath);
}
}
@@ -136,13 +143,44 @@ namespace ts.BuilderState {
}
const fileName = resolvedTypeReferenceDirective.resolvedFileName!; // TODO: GH#18217
const typeFilePath = toPath(fileName, sourceFileDirectory, getCanonicalFileName);
const typeFilePath = getReferencedFileFromFileName(program, fileName, sourceFileDirectory, getCanonicalFileName);
addReferencedFile(typeFilePath);
});
}
// Add module augmentation as references
if (sourceFile.moduleAugmentations.length) {
const checker = program.getTypeChecker();
for (const moduleName of sourceFile.moduleAugmentations) {
if (!isStringLiteral(moduleName)) { continue; }
const symbol = checker.getSymbolAtLocation(moduleName);
if (!symbol) { continue; }
// Add any file other than our own as reference
addReferenceFromAmbientModule(symbol);
}
}
// From ambient modules
for (const ambientModule of program.getTypeChecker().getAmbientModules()) {
if (ambientModule.declarations.length > 1) {
addReferenceFromAmbientModule(ambientModule);
}
}
return referencedFiles;
function addReferenceFromAmbientModule(symbol: Symbol) {
// Add any file other than our own as reference
for (const declaration of symbol.declarations) {
const declarationSourceFile = getSourceFileOfNode(declaration);
if (declarationSourceFile &&
declarationSourceFile !== sourceFile) {
addReferencedFile(declarationSourceFile.resolvedPath);
}
}
}
function addReferencedFile(referencedPath: Path) {
if (!referencedFiles) {
referencedFiles = createMap<true>();
+1311 -1182
View File
File diff suppressed because it is too large Load Diff
+191 -94
View File
@@ -62,7 +62,8 @@ namespace ts {
/* @internal */
export const libMap = createMapFromEntries(libEntries);
const commonOptionsWithBuild: CommandLineOption[] = [
/* @internal */
export const commonOptionsWithBuild: CommandLineOption[] = [
{
name: "help",
shortName: "h",
@@ -76,6 +77,14 @@ namespace ts {
shortName: "?",
type: "boolean"
},
{
name: "watch",
shortName: "w",
type: "boolean",
showInSimplifiedHelpView: true,
category: Diagnostics.Command_line_Options,
description: Diagnostics.Watch_input_files,
},
{
name: "preserveWatchOutput",
type: "boolean",
@@ -84,12 +93,23 @@ namespace ts {
description: Diagnostics.Whether_to_keep_outdated_console_output_in_watch_mode_instead_of_clearing_the_screen,
},
{
name: "watch",
shortName: "w",
name: "listFiles",
type: "boolean",
showInSimplifiedHelpView: true,
category: Diagnostics.Command_line_Options,
description: Diagnostics.Watch_input_files,
category: Diagnostics.Advanced_Options,
description: Diagnostics.Print_names_of_files_part_of_the_compilation
},
{
name: "listEmittedFiles",
type: "boolean",
category: Diagnostics.Advanced_Options,
description: Diagnostics.Print_names_of_generated_files_part_of_the_compilation
},
{
name: "traceResolution",
type: "boolean",
category: Diagnostics.Advanced_Options,
description: Diagnostics.Enable_tracing_of_the_name_resolution_process
},
];
@@ -159,6 +179,8 @@ namespace ts {
es2018: ScriptTarget.ES2018,
esnext: ScriptTarget.ESNext,
}),
affectsSourceFile: true,
affectsModuleResolution: true,
paramType: Diagnostics.VERSION,
showInSimplifiedHelpView: true,
category: Diagnostics.Basic_Options,
@@ -177,6 +199,7 @@ namespace ts {
es2015: ModuleKind.ES2015,
esnext: ModuleKind.ESNext
}),
affectsModuleResolution: true,
paramType: Diagnostics.KIND,
showInSimplifiedHelpView: true,
category: Diagnostics.Basic_Options,
@@ -189,6 +212,7 @@ namespace ts {
name: "lib",
type: libMap
},
affectsModuleResolution: true,
showInSimplifiedHelpView: true,
category: Diagnostics.Basic_Options,
description: Diagnostics.Specify_library_files_to_be_included_in_the_compilation
@@ -196,6 +220,7 @@ namespace ts {
{
name: "allowJs",
type: "boolean",
affectsModuleResolution: true,
showInSimplifiedHelpView: true,
category: Diagnostics.Basic_Options,
description: Diagnostics.Allow_javascript_files_to_be_compiled
@@ -213,6 +238,7 @@ namespace ts {
"react-native": JsxEmit.ReactNative,
"react": JsxEmit.React
}),
affectsSourceFile: true,
paramType: Diagnostics.KIND,
showInSimplifiedHelpView: true,
category: Diagnostics.Basic_Options,
@@ -323,6 +349,7 @@ namespace ts {
{
name: "noImplicitAny",
type: "boolean",
affectsSemanticDiagnostics: true,
strictFlag: true,
showInSimplifiedHelpView: true,
category: Diagnostics.Strict_Type_Checking_Options,
@@ -331,6 +358,7 @@ namespace ts {
{
name: "strictNullChecks",
type: "boolean",
affectsSemanticDiagnostics: true,
strictFlag: true,
showInSimplifiedHelpView: true,
category: Diagnostics.Strict_Type_Checking_Options,
@@ -339,14 +367,24 @@ namespace ts {
{
name: "strictFunctionTypes",
type: "boolean",
affectsSemanticDiagnostics: true,
strictFlag: true,
showInSimplifiedHelpView: true,
category: Diagnostics.Strict_Type_Checking_Options,
description: Diagnostics.Enable_strict_checking_of_function_types
},
{
name: "strictBindCallApply",
type: "boolean",
strictFlag: true,
showInSimplifiedHelpView: true,
category: Diagnostics.Strict_Type_Checking_Options,
description: Diagnostics.Enable_strict_bind_call_and_apply_methods_on_functions
},
{
name: "strictPropertyInitialization",
type: "boolean",
affectsSemanticDiagnostics: true,
strictFlag: true,
showInSimplifiedHelpView: true,
category: Diagnostics.Strict_Type_Checking_Options,
@@ -355,6 +393,7 @@ namespace ts {
{
name: "noImplicitThis",
type: "boolean",
affectsSemanticDiagnostics: true,
strictFlag: true,
showInSimplifiedHelpView: true,
category: Diagnostics.Strict_Type_Checking_Options,
@@ -363,6 +402,7 @@ namespace ts {
{
name: "alwaysStrict",
type: "boolean",
affectsSourceFile: true,
strictFlag: true,
showInSimplifiedHelpView: true,
category: Diagnostics.Strict_Type_Checking_Options,
@@ -397,6 +437,7 @@ namespace ts {
{
name: "noFallthroughCasesInSwitch",
type: "boolean",
affectsBindDiagnostics: true,
affectsSemanticDiagnostics: true,
showInSimplifiedHelpView: true,
category: Diagnostics.Additional_Checks,
@@ -410,6 +451,7 @@ namespace ts {
node: ModuleResolutionKind.NodeJs,
classic: ModuleResolutionKind.Classic,
}),
affectsModuleResolution: true,
paramType: Diagnostics.STRATEGY,
category: Diagnostics.Module_Resolution_Options,
description: Diagnostics.Specify_module_resolution_strategy_Colon_node_Node_js_or_classic_TypeScript_pre_1_6,
@@ -417,6 +459,7 @@ namespace ts {
{
name: "baseUrl",
type: "string",
affectsModuleResolution: true,
isFilePath: true,
category: Diagnostics.Module_Resolution_Options,
description: Diagnostics.Base_directory_to_resolve_non_absolute_module_names
@@ -426,6 +469,7 @@ namespace ts {
// use type = object to copy the value as-is
name: "paths",
type: "object",
affectsModuleResolution: true,
isTSConfigOnly: true,
category: Diagnostics.Module_Resolution_Options,
description: Diagnostics.A_series_of_entries_which_re_map_imports_to_lookup_locations_relative_to_the_baseUrl
@@ -441,6 +485,7 @@ namespace ts {
type: "string",
isFilePath: true
},
affectsModuleResolution: true,
category: Diagnostics.Module_Resolution_Options,
description: Diagnostics.List_of_root_folders_whose_combined_content_represents_the_structure_of_the_project_at_runtime
},
@@ -452,6 +497,7 @@ namespace ts {
type: "string",
isFilePath: true
},
affectsModuleResolution: true,
category: Diagnostics.Module_Resolution_Options,
description: Diagnostics.List_of_folders_to_include_type_definitions_from
},
@@ -462,6 +508,7 @@ namespace ts {
name: "types",
type: "string"
},
affectsModuleResolution: true,
showInSimplifiedHelpView: true,
category: Diagnostics.Module_Resolution_Options,
description: Diagnostics.Type_declaration_files_to_be_included_in_compilation
@@ -549,30 +596,12 @@ namespace ts {
category: Diagnostics.Advanced_Options,
description: Diagnostics.Show_verbose_diagnostic_information
},
{
name: "traceResolution",
type: "boolean",
category: Diagnostics.Advanced_Options,
description: Diagnostics.Enable_tracing_of_the_name_resolution_process
},
{
name: "resolveJsonModule",
type: "boolean",
category: Diagnostics.Advanced_Options,
description: Diagnostics.Include_modules_imported_with_json_extension
},
{
name: "listFiles",
type: "boolean",
category: Diagnostics.Advanced_Options,
description: Diagnostics.Print_names_of_files_part_of_the_compilation
},
{
name: "listEmittedFiles",
type: "boolean",
category: Diagnostics.Advanced_Options,
description: Diagnostics.Print_names_of_generated_files_part_of_the_compilation
},
{
name: "out",
@@ -632,12 +661,14 @@ namespace ts {
{
name: "noLib",
type: "boolean",
affectsModuleResolution: true,
category: Diagnostics.Advanced_Options,
description: Diagnostics.Do_not_include_the_default_library_file_lib_d_ts
},
{
name: "noResolve",
type: "boolean",
affectsModuleResolution: true,
category: Diagnostics.Advanced_Options,
description: Diagnostics.Do_not_add_triple_slash_references_or_imported_modules_to_the_list_of_compiled_files
},
@@ -650,6 +681,7 @@ namespace ts {
{
name: "disableSizeLimit",
type: "boolean",
affectsSourceFile: true,
category: Diagnostics.Advanced_Options,
description: Diagnostics.Disable_size_limitations_on_JavaScript_projects
},
@@ -695,6 +727,7 @@ namespace ts {
{
name: "allowUnusedLabels",
type: "boolean",
affectsBindDiagnostics: true,
affectsSemanticDiagnostics: true,
category: Diagnostics.Advanced_Options,
description: Diagnostics.Do_not_report_errors_on_unused_labels
@@ -702,6 +735,7 @@ namespace ts {
{
name: "allowUnreachableCode",
type: "boolean",
affectsBindDiagnostics: true,
affectsSemanticDiagnostics: true,
category: Diagnostics.Advanced_Options,
description: Diagnostics.Do_not_report_errors_on_unreachable_code
@@ -729,6 +763,7 @@ namespace ts {
{
name: "maxNodeModuleJsDepth",
type: "number",
// TODO: GH#27108 affectsModuleResolution: true,
category: Diagnostics.Advanced_Options,
description: Diagnostics.The_maximum_dependency_depth_to_search_under_node_modules_and_load_JavaScript_files
},
@@ -758,6 +793,18 @@ namespace ts {
}
];
/* @internal */
export const semanticDiagnosticsOptionDeclarations: ReadonlyArray<CommandLineOption> =
optionDeclarations.filter(option => !!option.affectsSemanticDiagnostics);
/* @internal */
export const moduleResolutionOptionDeclarations: ReadonlyArray<CommandLineOption> =
optionDeclarations.filter(option => !!option.affectsModuleResolution);
/* @internal */
export const sourceFileAffectingCompilerOptions: ReadonlyArray<CommandLineOption> = optionDeclarations.filter(option =>
!!option.affectsSourceFile || !!option.affectsModuleResolution || !!option.affectsBindDiagnostics);
/* @internal */
export const buildOpts: CommandLineOption[] = [
...commonOptionsWithBuild,
@@ -903,17 +950,27 @@ namespace ts {
}
}
export function parseCommandLine(commandLine: ReadonlyArray<string>, readFile?: (path: string) => string | undefined): ParsedCommandLine {
const options: CompilerOptions = {};
/* @internal */
export interface OptionsBase {
[option: string]: CompilerOptionsValue | undefined;
}
/** Tuple with error messages for 'unknown compiler option', 'option requires type' */
type ParseCommandLineWorkerDiagnostics = [DiagnosticMessage, DiagnosticMessage];
function parseCommandLineWorker(
getOptionNameMap: () => OptionNameMap,
[unknownOptionDiagnostic, optionTypeMismatchDiagnostic]: ParseCommandLineWorkerDiagnostics,
commandLine: ReadonlyArray<string>,
readFile?: (path: string) => string | undefined) {
const options = {} as OptionsBase;
const fileNames: string[] = [];
const projectReferences: ProjectReference[] | undefined = undefined;
const errors: Diagnostic[] = [];
parseStrings(commandLine);
return {
options,
fileNames,
projectReferences,
errors
};
@@ -926,7 +983,7 @@ namespace ts {
parseResponseFile(s.slice(1));
}
else if (s.charCodeAt(0) === CharacterCodes.minus) {
const opt = getOptionFromName(s.slice(s.charCodeAt(1) === CharacterCodes.minus ? 2 : 1), /*allowShort*/ true);
const opt = getOptionDeclarationFromName(getOptionNameMap, s.slice(s.charCodeAt(1) === CharacterCodes.minus ? 2 : 1), /*allowShort*/ true);
if (opt) {
if (opt.isTSConfigOnly) {
errors.push(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file, opt.name));
@@ -934,7 +991,7 @@ namespace ts {
else {
// Check to see if no argument was provided (e.g. "--locale" is the last command-line argument).
if (!args[i] && opt.type !== "boolean") {
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_expects_an_argument, opt.name));
errors.push(createCompilerDiagnostic(optionTypeMismatchDiagnostic, opt.name));
}
switch (opt.type) {
@@ -956,7 +1013,7 @@ namespace ts {
i++;
break;
case "list":
const result = parseListTypeOption(<CommandLineOptionOfListType>opt, args[i], errors);
const result = parseListTypeOption(opt, args[i], errors);
options[opt.name] = result || [];
if (result) {
i++;
@@ -971,7 +1028,7 @@ namespace ts {
}
}
else {
errors.push(createCompilerDiagnostic(Diagnostics.Unknown_compiler_option_0, s));
errors.push(createCompilerDiagnostic(unknownOptionDiagnostic, s));
}
}
else {
@@ -1014,13 +1071,19 @@ namespace ts {
}
}
export function parseCommandLine(commandLine: ReadonlyArray<string>, readFile?: (path: string) => string | undefined): ParsedCommandLine {
return parseCommandLineWorker(getOptionNameMap, [
Diagnostics.Unknown_compiler_option_0,
Diagnostics.Compiler_option_0_expects_an_argument
], commandLine, readFile);
}
/** @internal */
export function getOptionFromName(optionName: string, allowShort?: boolean): CommandLineOption | undefined {
return getOptionDeclarationFromName(getOptionNameMap, optionName, allowShort);
}
/*@internal*/
export function getOptionDeclarationFromName(getOptionNameMap: () => OptionNameMap, optionName: string, allowShort = false): CommandLineOption | undefined {
function getOptionDeclarationFromName(getOptionNameMap: () => OptionNameMap, optionName: string, allowShort = false): CommandLineOption | undefined {
optionName = optionName.toLowerCase();
const { optionNameMap, shortOptionNames } = getOptionNameMap();
// Try to translate short option names to their full equivalents.
@@ -1044,25 +1107,11 @@ namespace ts {
export function parseBuildCommand(args: string[]): ParsedBuildCommand {
let buildOptionNameMap: OptionNameMap | undefined;
const returnBuildOptionNameMap = () => (buildOptionNameMap || (buildOptionNameMap = createOptionNameMap(buildOpts)));
const buildOptions: BuildOptions = {};
const projects: string[] = [];
let errors: Diagnostic[] | undefined;
for (const arg of args) {
if (arg.charCodeAt(0) === CharacterCodes.minus) {
const opt = getOptionDeclarationFromName(returnBuildOptionNameMap, arg.slice(arg.charCodeAt(1) === CharacterCodes.minus ? 2 : 1), /*allowShort*/ true);
if (opt) {
buildOptions[opt.name as keyof BuildOptions] = true;
}
else {
(errors || (errors = [])).push(createCompilerDiagnostic(Diagnostics.Unknown_build_option_0, arg));
}
}
else {
// Not a flag, parse as filename
projects.push(arg);
}
}
const { options, fileNames: projects, errors } = parseCommandLineWorker(returnBuildOptionNameMap, [
Diagnostics.Unknown_build_option_0,
Diagnostics.Build_option_0_requires_a_value_of_type_1
], args);
const buildOptions = options as BuildOptions;
if (projects.length === 0) {
// tsc -b invoked with no extra arguments; act as if invoked with "tsc -b ."
@@ -1071,19 +1120,19 @@ namespace ts {
// Nonsensical combinations
if (buildOptions.clean && buildOptions.force) {
(errors || (errors = [])).push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "force"));
errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "force"));
}
if (buildOptions.clean && buildOptions.verbose) {
(errors || (errors = [])).push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "verbose"));
errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "verbose"));
}
if (buildOptions.clean && buildOptions.watch) {
(errors || (errors = [])).push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "watch"));
errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "watch"));
}
if (buildOptions.watch && buildOptions.dry) {
(errors || (errors = [])).push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "watch", "dry"));
errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "watch", "dry"));
}
return { buildOptions, projects, errors: errors || emptyArray };
return { buildOptions, projects, errors };
}
function getDiagnosticText(_message: DiagnosticMessage, ..._args: any[]): string {
@@ -1480,7 +1529,12 @@ namespace ts {
elements: NodeArray<Expression>,
elementOption: CommandLineOption | undefined
): any[] | void {
return (returnValue ? elements.map : elements.forEach).call(elements, (element: Expression) => convertPropertyValueToJson(element, elementOption));
if (!returnValue) {
return elements.forEach(element => convertPropertyValueToJson(element, elementOption));
}
// Filter out invalid values
return filter(elements.map(element => convertPropertyValueToJson(element, elementOption)), v => v !== undefined);
}
function convertPropertyValueToJson(valueExpression: Expression, option: CommandLineOption | undefined): any {
@@ -1613,7 +1667,7 @@ namespace ts {
return undefined;
}
else if (optionDefinition.type === "list") {
return getCustomTypeMapOfCommandLineOption((<CommandLineOptionOfListType>optionDefinition).element);
return getCustomTypeMapOfCommandLineOption(optionDefinition.element);
}
else {
return (<CommandLineOptionOfCustomType>optionDefinition).type;
@@ -1677,7 +1731,7 @@ namespace ts {
case "object":
return {};
default:
return (option as CommandLineOptionOfCustomType).type.keys().next().value;
return option.type.keys().next().value;
}
}
@@ -1825,7 +1879,8 @@ namespace ts {
const options = extend(existingOptions, parsedConfig.options || {});
options.configFilePath = configFileName && normalizeSlashes(configFileName);
setConfigFileInOptions(options, sourceFile);
const { fileNames, wildcardDirectories, spec, projectReferences } = getFileNames();
let projectReferences: ProjectReference[] | undefined;
const { fileNames, wildcardDirectories, spec } = getFileNames();
return {
options,
fileNames,
@@ -1843,8 +1898,22 @@ namespace ts {
if (hasProperty(raw, "files") && !isNullOrUndefined(raw.files)) {
if (isArray(raw.files)) {
filesSpecs = <ReadonlyArray<string>>raw.files;
if (filesSpecs.length === 0) {
createCompilerDiagnosticOnlyIfJson(Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json");
const hasReferences = hasProperty(raw, "references") && !isNullOrUndefined(raw.references);
const hasZeroOrNoReferences = !hasReferences || raw.references.length === 0;
const hasExtends = hasProperty(raw, "extends");
if (filesSpecs.length === 0 && hasZeroOrNoReferences && !hasExtends) {
if (sourceFile) {
const fileName = configFileName || "tsconfig.json";
const diagnosticMessage = Diagnostics.The_files_list_in_config_file_0_is_empty;
const nodeValue = firstDefined(getTsConfigPropArray(sourceFile, "files"), property => property.initializer);
const error = nodeValue
? createDiagnosticForNodeInSourceFile(sourceFile, nodeValue, diagnosticMessage, fileName)
: createCompilerDiagnostic(diagnosticMessage, fileName);
errors.push(error);
}
else {
createCompilerDiagnosticOnlyIfJson(Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json");
}
}
}
else {
@@ -1885,19 +1954,18 @@ namespace ts {
}
const result = matchFileNames(filesSpecs, includeSpecs, excludeSpecs, configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath, options, host, errors, extraFileExtensions, sourceFile);
if (result.fileNames.length === 0 && !hasProperty(raw, "files") && resolutionStack.length === 0 && !hasProperty(raw, "references")) {
if (shouldReportNoInputFiles(result, canJsonReportNoInutFiles(raw), resolutionStack)) {
errors.push(getErrorForNoInputFiles(result.spec, configFileName));
}
if (hasProperty(raw, "references") && !isNullOrUndefined(raw.references)) {
if (isArray(raw.references)) {
const references: ProjectReference[] = [];
for (const ref of raw.references) {
if (typeof ref.path !== "string") {
createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "reference.path", "string");
}
else {
references.push({
(projectReferences || (projectReferences = [])).push({
path: getNormalizedAbsolutePath(ref.path, basePath),
originalPath: ref.path,
prepend: ref.prepend,
@@ -1905,7 +1973,6 @@ namespace ts {
});
}
}
result.projectReferences = references;
}
else {
createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "references", "Array");
@@ -1922,13 +1989,11 @@ namespace ts {
}
}
/*@internal*/
export function isErrorNoInputFiles(error: Diagnostic) {
function isErrorNoInputFiles(error: Diagnostic) {
return error.code === Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code;
}
/*@internal*/
export function getErrorForNoInputFiles({ includeSpecs, excludeSpecs }: ConfigFileSpecs, configFileName: string | undefined) {
function getErrorForNoInputFiles({ includeSpecs, excludeSpecs }: ConfigFileSpecs, configFileName: string | undefined) {
return createCompilerDiagnostic(
Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2,
configFileName || "tsconfig.json",
@@ -1936,6 +2001,27 @@ namespace ts {
JSON.stringify(excludeSpecs || []));
}
function shouldReportNoInputFiles(result: ExpandResult, canJsonReportNoInutFiles: boolean, resolutionStack?: Path[]) {
return result.fileNames.length === 0 && canJsonReportNoInutFiles && (!resolutionStack || resolutionStack.length === 0);
}
/*@internal*/
export function canJsonReportNoInutFiles(raw: any) {
return !hasProperty(raw, "files") && !hasProperty(raw, "references");
}
/*@internal*/
export function updateErrorForNoInputFiles(result: ExpandResult, configFileName: string, configFileSpecs: ConfigFileSpecs, configParseDiagnostics: Diagnostic[], canJsonReportNoInutFiles: boolean) {
const existingErrors = configParseDiagnostics.length;
if (shouldReportNoInputFiles(result, canJsonReportNoInutFiles)) {
configParseDiagnostics.push(getErrorForNoInputFiles(configFileSpecs, configFileName));
}
else {
filterMutate(configParseDiagnostics, error => !isErrorNoInputFiles(error));
}
return existingErrors !== configParseDiagnostics.length;
}
interface ParsedTsconfig {
raw: any;
options?: CompilerOptions;
@@ -1978,7 +2064,7 @@ namespace ts {
if (ownConfig.extendedConfigPath) {
// copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios.
resolutionStack = resolutionStack.concat([resolvedPath]);
const extendedConfig = getExtendedConfig(sourceFile!, ownConfig.extendedConfigPath, host, basePath, resolutionStack, errors);
const extendedConfig = getExtendedConfig(sourceFile, ownConfig.extendedConfigPath, host, basePath, resolutionStack, errors);
if (extendedConfig && isSuccessfulParsedTsconfig(extendedConfig)) {
const baseRaw = extendedConfig.raw;
const raw = ownConfig.raw;
@@ -2067,11 +2153,6 @@ namespace ts {
createDiagnosticForNodeInSourceFile(sourceFile, valueNode, message, arg0)
);
return;
case "files":
if ((<ReadonlyArray<string>>value).length === 0) {
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueNode, Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json"));
}
return;
}
},
onSetUnknownOptionKeyValueInRoot(key: string, keyNode: PropertyName, _value: CompilerOptionsValue, _valueNode: Expression) {
@@ -2081,6 +2162,7 @@ namespace ts {
}
};
const json = convertToObjectWorker(sourceFile, errors, /*returnValue*/ true, getTsconfigRootOptionsMap(), optionsIterator);
if (!typeAcquisition) {
if (typingOptionstypeAcquisition) {
typeAcquisition = (typingOptionstypeAcquisition.enableAutoDiscovery !== undefined) ?
@@ -2123,7 +2205,7 @@ namespace ts {
}
function getExtendedConfig(
sourceFile: TsConfigSourceFile,
sourceFile: TsConfigSourceFile | undefined,
extendedConfigPath: string,
host: ParseConfigHost,
basePath: string,
@@ -2132,7 +2214,7 @@ namespace ts {
): ParsedTsconfig | undefined {
const extendedResult = readJsonConfigFile(extendedConfigPath, path => host.readFile(path));
if (sourceFile) {
(sourceFile.extendedSourceFiles || (sourceFile.extendedSourceFiles = [])).push(extendedResult.fileName);
sourceFile.extendedSourceFiles = [extendedResult.fileName];
}
if (extendedResult.parseDiagnostics.length) {
errors.push(...extendedResult.parseDiagnostics);
@@ -2142,8 +2224,8 @@ namespace ts {
const extendedDirname = getDirectoryPath(extendedConfigPath);
const extendedConfig = parseConfig(/*json*/ undefined, extendedResult, host, extendedDirname,
getBaseFileName(extendedConfigPath), resolutionStack, errors);
if (sourceFile) {
sourceFile.extendedSourceFiles!.push(...extendedResult.extendedSourceFiles!);
if (sourceFile && extendedResult.extendedSourceFiles) {
sourceFile.extendedSourceFiles!.push(...extendedResult.extendedSourceFiles);
}
if (isSuccessfulParsedTsconfig(extendedConfig)) {
@@ -2256,7 +2338,7 @@ namespace ts {
function normalizeOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue {
if (isNullOrUndefined(value)) return undefined;
if (option.type === "list") {
const listOption = <CommandLineOptionOfListType>option;
const listOption = option;
if (listOption.element.isFilePath || !isString(listOption.element.type)) {
return <CompilerOptionsValue>filter(map(value, v => normalizeOptionValue(listOption.element, basePath, v)), v => !!v);
}
@@ -2398,7 +2480,7 @@ namespace ts {
// new entries in these paths.
const wildcardDirectories = getWildcardDirectories(validatedIncludeSpecs, validatedExcludeSpecs, basePath, host.useCaseSensitiveFileNames);
const spec: ConfigFileSpecs = { filesSpecs, referencesSpecs: undefined, includeSpecs, excludeSpecs, validatedIncludeSpecs, validatedExcludeSpecs, wildcardDirectories };
const spec: ConfigFileSpecs = { filesSpecs, includeSpecs, excludeSpecs, validatedIncludeSpecs, validatedExcludeSpecs, wildcardDirectories };
return getFileNamesFromConfigSpecs(spec, basePath, options, host, extraFileExtensions);
}
@@ -2427,11 +2509,16 @@ namespace ts {
// via wildcard, and to handle extension priority.
const wildcardFileMap = createMap<string>();
// Wildcard paths of json files (provided via the "includes" array in tsconfig.json) are stored in a
// file map with a possibly case insensitive key. We use this map to store paths matched
// via wildcard of *.json kind
const wildCardJsonFileMap = createMap<string>();
const { filesSpecs, validatedIncludeSpecs, validatedExcludeSpecs, wildcardDirectories } = spec;
// Rather than requery this for each file and filespec, we query the supported extensions
// once and store it on the expansion context.
const supportedExtensions = getSupportedExtensions(options, extraFileExtensions);
const supportedExtensionsWithJsonIfResolveJsonModule = getSuppoertedExtensionsWithJsonIfResolveJsonModule(options, supportedExtensions);
// Literal files are always included verbatim. An "include" or "exclude" specification cannot
// remove a literal file.
@@ -2442,8 +2529,25 @@ namespace ts {
}
}
let jsonOnlyIncludeRegexes: ReadonlyArray<RegExp> | undefined;
if (validatedIncludeSpecs && validatedIncludeSpecs.length > 0) {
for (const file of host.readDirectory(basePath, supportedExtensions, validatedExcludeSpecs, validatedIncludeSpecs, /*depth*/ undefined)) {
for (const file of host.readDirectory(basePath, supportedExtensionsWithJsonIfResolveJsonModule, validatedExcludeSpecs, validatedIncludeSpecs, /*depth*/ undefined)) {
if (fileExtensionIs(file, Extension.Json)) {
// Valid only if *.json specified
if (!jsonOnlyIncludeRegexes) {
const includes = validatedIncludeSpecs.filter(s => endsWith(s, Extension.Json));
const includeFilePatterns = map(getRegularExpressionsForWildcards(includes, basePath, "files"), pattern => `^${pattern}$`);
jsonOnlyIncludeRegexes = includeFilePatterns ? includeFilePatterns.map(pattern => getRegexFromPattern(pattern, host.useCaseSensitiveFileNames)) : emptyArray;
}
const includeIndex = findIndex(jsonOnlyIncludeRegexes, re => re.test(file));
if (includeIndex !== -1) {
const key = keyMapper(file);
if (!literalFileMap.has(key) && !wildCardJsonFileMap.has(key)) {
wildCardJsonFileMap.set(key, file);
}
}
continue;
}
// If we have already included a literal or wildcard path with a
// higher priority extension, we should skip this file.
//
@@ -2469,16 +2573,9 @@ namespace ts {
const literalFiles = arrayFrom(literalFileMap.values());
const wildcardFiles = arrayFrom(wildcardFileMap.values());
const projectReferences = spec.referencesSpecs && spec.referencesSpecs.map((r): ProjectReference => {
return {
...r,
path: getNormalizedAbsolutePath(r.path, basePath)
};
});
return {
fileNames: literalFiles.concat(wildcardFiles),
projectReferences,
fileNames: literalFiles.concat(wildcardFiles, arrayFrom(wildCardJsonFileMap.values())),
wildcardDirectories,
spec
};
@@ -2648,7 +2745,7 @@ namespace ts {
case "boolean":
return typeof value === "boolean" ? value : "";
case "list":
const elementType = (option as CommandLineOptionOfListType).element;
const elementType = option.element;
return isArray(value) ? value.map(v => getOptionValueWithEmptyStrings(v, elementType)) : "";
default:
return forEachEntry(option.type, (optionEnumValue, optionStringValue) => {
+2 -2
View File
@@ -220,7 +220,7 @@ namespace ts {
}
if (extendedDiagnostics) {
performance.mark("beginEmitBodyWithDetachedCommetns");
performance.mark("beginEmitBodyWithDetachedComments");
}
if (!skipTrailingComments) {
@@ -231,7 +231,7 @@ namespace ts {
}
if (extendedDiagnostics) {
performance.measure("commentTime", "beginEmitBodyWithDetachedCommetns");
performance.measure("commentTime", "beginEmitBodyWithDetachedComments");
}
}
+16 -6
View File
@@ -1,7 +1,7 @@
namespace ts {
// WARNING: The script `configureNightly.ts` uses a regexp to parse out these values.
// If changing the text in this section, be sure to test `configureNightly` too.
export const versionMajorMinor = "3.1";
export const versionMajorMinor = "3.2";
/** The version of the TypeScript compiler release */
export const version = `${versionMajorMinor}.0-dev`;
}
@@ -65,6 +65,7 @@ namespace ts {
/* @internal */
namespace ts {
export const emptyArray: never[] = [] as never[];
/** Create a MapLike with good performance. */
function createDictionaryObject<T>(): MapLike<T> {
@@ -841,7 +842,7 @@ namespace ts {
return deduplicateSorted(sort(array, comparer), equalityComparer || comparer);
}
export function arrayIsEqualTo<T>(array1: ReadonlyArray<T> | undefined, array2: ReadonlyArray<T> | undefined, equalityComparer: (a: T, b: T) => boolean = equateValues): boolean {
export function arrayIsEqualTo<T>(array1: ReadonlyArray<T> | undefined, array2: ReadonlyArray<T> | undefined, equalityComparer: (a: T, b: T, index: number) => boolean = equateValues): boolean {
if (!array1 || !array2) {
return array1 === array2;
}
@@ -851,7 +852,7 @@ namespace ts {
}
for (let i = 0; i < array1.length; i++) {
if (!equalityComparer(array1[i], array2[i])) {
if (!equalityComparer(array1[i], array2[i], i)) {
return false;
}
}
@@ -1408,9 +1409,12 @@ namespace ts {
/**
* Tests whether a value is string
*/
export function isString(text: any): text is string {
export function isString(text: unknown): text is string {
return typeof text === "string";
}
export function isNumber(x: unknown): x is number {
return typeof x === "number";
}
export function tryCast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut | undefined;
export function tryCast<T>(value: T, test: (value: T) => boolean): T | undefined;
@@ -1533,6 +1537,7 @@ namespace ts {
* Every function should be assignable to this, but this should not be assignable to every function.
*/
export type AnyFunction = (...args: never[]) => void;
export type AnyConstructor = new (...args: unknown[]) => unknown;
export namespace Debug {
export let currentAssertionLevel = AssertionLevel.None;
@@ -1597,8 +1602,9 @@ namespace ts {
return value;
}
export function assertNever(member: never, message?: string, stackCrawlMark?: AnyFunction): never {
return fail(message || `Illegal value: ${member}`, stackCrawlMark || assertNever);
export function assertNever(member: never, message = "Illegal value:", stackCrawlMark?: AnyFunction): never {
const detail = "kind" in member && "pos" in member ? "SyntaxKind: " + showSyntaxKind(member as Node) : JSON.stringify(member);
return fail(`${message} ${detail}`, stackCrawlMark || assertNever);
}
export function getFunctionName(func: AnyFunction) {
@@ -2124,4 +2130,8 @@ namespace ts {
deleted(oldItems[oldIndex++]);
}
}
export function fill<T>(length: number, cb: (index: number) => T): T[] {
return new Array(length).fill(0).map((_, i) => cb(i));
}
}
+96 -13
View File
@@ -2088,6 +2088,38 @@
"category": "Error",
"code": 2577
},
"Cannot find name '{0}'. Do you need to install type definitions for node? Try `npm i @types/node`.": {
"category": "Error",
"code": 2580
},
"Cannot find name '{0}'. Do you need to install type definitions for jQuery? Try `npm i @types/jquery`.": {
"category": "Error",
"code": 2581
},
"Cannot find name '{0}'. Do you need to install type definitions for a test runner? Try `npm i @types/jest` or `npm i @types/mocha`.": {
"category": "Error",
"code": 2582
},
"Cannot find name '{0}'. Do you need to change your target library? Try changing the `lib` compiler option to es2015 or later.": {
"category": "Error",
"code": 2583
},
"Cannot find name '{0}'. Do you need to change your target library? Try changing the `lib` compiler option to include 'dom'.": {
"category": "Error",
"code": 2584
},
"'{0}' only refers to a type, but is being used as a value here. Do you need to change your target library? Try changing the `lib` compiler option to es2015 or later.": {
"category": "Error",
"code": 2585
},
"Enum type '{0}' circularly references itself.": {
"category": "Error",
"code": 2586
},
"JSDoc type '{0}' circularly references itself.": {
"category": "Error",
"code": 2587
},
"JSX element attributes type '{0}' may not be a union type.": {
"category": "Error",
"code": 2600
@@ -2453,9 +2485,9 @@
"category": "Error",
"code": 2732
},
"Index '{0}' is out-of-bounds in tuple of length {1}.": {
"It is highly likely that you are missing a semicolon.": {
"category": "Error",
"code": 2733
"code": 2734
},
"Import declaration '{0}' is using private name '{1}'.": {
@@ -2920,7 +2952,10 @@
"category": "Error",
"code": 5072
},
"Build option '{0}' requires a value of type {1}.": {
"category": "Error",
"code": 5073
},
"Generates a sourcemap for each corresponding '.d.ts' file.": {
"category": "Message",
@@ -3294,7 +3329,7 @@
"category": "Message",
"code": 6104
},
"Expected type of '{0}' field in 'package.json' to be 'string', got '{1}'.": {
"Expected type of '{0}' field in 'package.json' to be '{1}', got '{2}'.": {
"category": "Message",
"code": 6105
},
@@ -3692,6 +3727,42 @@
"category": "Error",
"code": 6205
},
"'package.json' has a 'typesVersions' field with version-specific path mappings.": {
"category": "Message",
"code": 6206
},
"'package.json' does not have a 'typesVersions' entry that matches version '{0}'.": {
"category": "Message",
"code": 6207
},
"'package.json' has a 'typesVersions' entry '{0}' that matches compiler version '{1}', looking for a pattern to match module name '{2}'.": {
"category": "Message",
"code": 6208
},
"'package.json' has a 'typesVersions' entry '{0}' that is not a valid semver range.": {
"category": "Message",
"code": 6209
},
"An argument for '{0}' was not provided.": {
"category": "Message",
"code": 6210
},
"An argument matching this binding pattern was not provided.": {
"category": "Message",
"code": 6211
},
"Did you mean to call this expression?": {
"category": "Message",
"code": 6212
},
"Did you mean to use 'new' with this expression?": {
"category": "Message",
"code": 6213
},
"Enable strict 'bind', 'call', and 'apply' methods on functions.": {
"category": "Message",
"code": 6214
},
"Projects to reference": {
"category": "Message",
@@ -3814,10 +3885,6 @@
"category": "Error",
"code": 6370
},
"Skipping clean because not all projects could be located": {
"category": "Error",
"code": 6371
},
"The expected type comes from property '{0}' which is declared here on type '{1}'": {
"category": "Message",
@@ -3827,6 +3894,10 @@
"category": "Message",
"code": 6501
},
"The expected type comes from the return type of this signature.": {
"category": "Message",
"code": 6502
},
"Variable '{0}' implicitly has an '{1}' type.": {
"category": "Error",
@@ -4086,6 +4157,10 @@
"category": "Error",
"code": 8030
},
"You cannot rename a module via a global import.": {
"category": "Error",
"code": 8031
},
"Only identifiers/qualified-names with optional type arguments are currently supported in a class 'extends' clause.": {
"category": "Error",
"code": 9002
@@ -4584,7 +4659,7 @@
"category": "Message",
"code": 95062
},
"Add missing enum member '{0}'": {
"category": "Message",
"code": 95063
@@ -4593,12 +4668,20 @@
"category": "Message",
"code": 95064
},
"Convert to async function":{
"Convert to async function": {
"category": "Message",
"code": 95065
"code": 95065
},
"Convert all to async functions": {
"category": "Message",
"code": 95066
"category": "Message",
"code": 95066
},
"Generate types for '{0}'": {
"category": "Message",
"code": 95067
},
"Generate types for all packages without types": {
"category": "Message",
"code": 95068
}
}
+43 -31
View File
@@ -41,20 +41,25 @@ namespace ts {
export function getOutputPathsFor(sourceFile: SourceFile | Bundle, host: EmitHost, forceDtsPaths: boolean): EmitFileNames {
const options = host.getCompilerOptions();
if (sourceFile.kind === SyntaxKind.Bundle) {
const jsFilePath = options.outFile || options.out!;
const sourceMapFilePath = getSourceMapFilePath(jsFilePath, options);
const declarationFilePath = (forceDtsPaths || getEmitDeclarations(options)) ? removeFileExtension(jsFilePath) + Extension.Dts : undefined;
const declarationMapPath = getAreDeclarationMapsEnabled(options) ? declarationFilePath + ".map" : undefined;
const outPath = options.outFile || options.out!;
const jsFilePath = options.emitDeclarationOnly ? undefined : outPath;
const sourceMapFilePath = jsFilePath && getSourceMapFilePath(jsFilePath, options);
const declarationFilePath = (forceDtsPaths || getEmitDeclarations(options)) ? removeFileExtension(outPath) + Extension.Dts : undefined;
const declarationMapPath = declarationFilePath && getAreDeclarationMapsEnabled(options) ? declarationFilePath + ".map" : undefined;
const bundleInfoPath = options.references && jsFilePath ? (removeFileExtension(jsFilePath) + infoExtension) : undefined;
return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, bundleInfoPath };
}
else {
const jsFilePath = getOwnEmitOutputFilePath(sourceFile.fileName, host, getOutputExtension(sourceFile, options));
const sourceMapFilePath = isJsonSourceFile(sourceFile) ? undefined : getSourceMapFilePath(jsFilePath, options);
const ownOutputFilePath = getOwnEmitOutputFilePath(sourceFile.fileName, host, getOutputExtension(sourceFile, options));
// If json file emits to the same location skip writing it, if emitDeclarationOnly skip writing it
const isJsonEmittedToSameLocation = isJsonSourceFile(sourceFile) &&
comparePaths(sourceFile.fileName, ownOutputFilePath, host.getCurrentDirectory(), !host.useCaseSensitiveFileNames()) === Comparison.EqualTo;
const jsFilePath = options.emitDeclarationOnly || isJsonEmittedToSameLocation ? undefined : ownOutputFilePath;
const sourceMapFilePath = !jsFilePath || isJsonSourceFile(sourceFile) ? undefined : getSourceMapFilePath(jsFilePath, options);
// For legacy reasons (ie, we have baselines capturing the behavior), js files don't report a .d.ts output path - this would only matter if `declaration` and `allowJs` were both on, which is currently an error
const isJs = isSourceFileJavaScript(sourceFile);
const isJs = isSourceFileJS(sourceFile);
const declarationFilePath = ((forceDtsPaths || getEmitDeclarations(options)) && !isJs) ? getDeclarationEmitOutputFilePath(sourceFile.fileName, host) : undefined;
const declarationMapPath = getAreDeclarationMapsEnabled(options) ? declarationFilePath + ".map" : undefined;
const declarationMapPath = declarationFilePath && getAreDeclarationMapsEnabled(options) ? declarationFilePath + ".map" : undefined;
return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, bundleInfoPath: undefined };
}
}
@@ -80,7 +85,7 @@ namespace ts {
}
if (options.jsx === JsxEmit.Preserve) {
if (isSourceFileJavaScript(sourceFile)) {
if (isSourceFileJS(sourceFile)) {
if (fileExtensionIs(sourceFile.fileName, Extension.Jsx)) {
return Extension.Jsx;
}
@@ -135,27 +140,33 @@ namespace ts {
if (!emitSkipped && emittedFilesList) {
if (!emitOnlyDtsFiles) {
emittedFilesList.push(jsFilePath);
}
if (sourceMapFilePath) {
emittedFilesList.push(sourceMapFilePath);
if (jsFilePath) {
emittedFilesList.push(jsFilePath);
}
if (sourceMapFilePath) {
emittedFilesList.push(sourceMapFilePath);
}
if (bundleInfoPath) {
emittedFilesList.push(bundleInfoPath);
}
}
if (declarationFilePath) {
emittedFilesList.push(declarationFilePath);
}
if (bundleInfoPath) {
emittedFilesList.push(bundleInfoPath);
if (declarationMapPath) {
emittedFilesList.push(declarationMapPath);
}
}
}
function emitJsFileOrBundle(sourceFileOrBundle: SourceFile | Bundle, jsFilePath: string, sourceMapFilePath: string | undefined, bundleInfoPath: string | undefined) {
// Make sure not to write js file and source map file if any of them cannot be written
if (host.isEmitBlocked(jsFilePath) || compilerOptions.noEmit || compilerOptions.emitDeclarationOnly) {
emitSkipped = true;
function emitJsFileOrBundle(sourceFileOrBundle: SourceFile | Bundle, jsFilePath: string | undefined, sourceMapFilePath: string | undefined, bundleInfoPath: string | undefined) {
if (emitOnlyDtsFiles || !jsFilePath) {
return;
}
if (emitOnlyDtsFiles) {
// Make sure not to write js file and source map file if any of them cannot be written
if ((jsFilePath && host.isEmitBlocked(jsFilePath)) || compilerOptions.noEmit) {
emitSkipped = true;
return;
}
// Transform the source files
@@ -187,12 +198,12 @@ namespace ts {
}
function emitDeclarationFileOrBundle(sourceFileOrBundle: SourceFile | Bundle, declarationFilePath: string | undefined, declarationMapPath: string | undefined) {
if (!(declarationFilePath && !isInJavaScriptFile(sourceFileOrBundle))) {
if (!(declarationFilePath && !isInJSFile(sourceFileOrBundle))) {
return;
}
const sourceFiles = isSourceFile(sourceFileOrBundle) ? [sourceFileOrBundle] : sourceFileOrBundle.sourceFiles;
// Setup and perform the transformation to retrieve declarations from the input files
const nonJsFiles = filter(sourceFiles, isSourceFileNotJavaScript);
const nonJsFiles = filter(sourceFiles, isSourceFileNotJS);
const inputListOrBundle = (compilerOptions.outFile || compilerOptions.out) ? [createBundle(nonJsFiles, !isSourceFile(sourceFileOrBundle) ? sourceFileOrBundle.prepends : undefined)] : nonJsFiles;
if (emitOnlyDtsFiles && !getEmitDeclarations(compilerOptions)) {
// Checker wont collect the linked aliases since thats only done when declaration is enabled.
@@ -1015,7 +1026,7 @@ namespace ts {
writeLines(helper.text);
}
else {
writeLines(helper.text(makeFileLevelOptmiisticUniqueName));
writeLines(helper.text(makeFileLevelOptimisticUniqueName));
}
helpersEmitted = true;
}
@@ -1041,7 +1052,7 @@ namespace ts {
// SyntaxKind.TemplateMiddle
// SyntaxKind.TemplateTail
function emitLiteral(node: LiteralLikeNode) {
const text = getLiteralTextOfNode(node);
const text = getLiteralTextOfNode(node, printerOptions.neverAsciiEscape);
if ((printerOptions.sourceMap || printerOptions.inlineSourceMap)
&& (node.kind === SyntaxKind.StringLiteral || isTemplateLiteralKind(node.kind))) {
writeLiteral(text);
@@ -1532,7 +1543,7 @@ namespace ts {
expression = skipPartiallyEmittedExpressions(expression);
if (isNumericLiteral(expression)) {
// check if numeric literal is a decimal literal that was originally written with a dot
const text = getLiteralTextOfNode(<LiteralExpression>expression);
const text = getLiteralTextOfNode(<LiteralExpression>expression, /*neverAsciiEscape*/ true);
return !expression.numericLiteralFlags
&& !stringContains(text, tokenToString(SyntaxKind.DotToken)!);
}
@@ -2818,7 +2829,8 @@ namespace ts {
const parameter = singleOrUndefined(parameters);
return parameter
&& parameter.pos === parentNode.pos // may not have parsed tokens between parent and parameter
&& !(isArrowFunction(parentNode) && parentNode.type) // arrow function may not have return type annotation
&& isArrowFunction(parentNode) // only arrow functions may have simple arrow head
&& !parentNode.type // arrow function may not have return type annotation
&& !some(parentNode.decorators) // parent may not have decorators
&& !some(parentNode.modifiers) // parent may not have modifiers
&& !some(parentNode.typeParameters) // parent may not have type parameters
@@ -3305,20 +3317,20 @@ namespace ts {
return getSourceTextOfNodeFromSourceFile(currentSourceFile, node, includeTrivia);
}
function getLiteralTextOfNode(node: LiteralLikeNode): string {
function getLiteralTextOfNode(node: LiteralLikeNode, neverAsciiEscape: boolean | undefined): string {
if (node.kind === SyntaxKind.StringLiteral && (<StringLiteral>node).textSourceNode) {
const textSourceNode = (<StringLiteral>node).textSourceNode!;
if (isIdentifier(textSourceNode)) {
return getEmitFlags(node) & EmitFlags.NoAsciiEscaping ?
return neverAsciiEscape || (getEmitFlags(node) & EmitFlags.NoAsciiEscaping) ?
`"${escapeString(getTextOfNode(textSourceNode))}"` :
`"${escapeNonAsciiString(getTextOfNode(textSourceNode))}"`;
}
else {
return getLiteralTextOfNode(textSourceNode);
return getLiteralTextOfNode(textSourceNode, neverAsciiEscape);
}
}
return getLiteralText(node, currentSourceFile);
return getLiteralText(node, currentSourceFile, neverAsciiEscape);
}
/**
@@ -3587,7 +3599,7 @@ namespace ts {
}
}
function makeFileLevelOptmiisticUniqueName(name: string) {
function makeFileLevelOptimisticUniqueName(name: string) {
return makeUniqueName(name, isFileLevelUniqueName, /*optimistic*/ true);
}
+1 -1
View File
@@ -235,7 +235,7 @@ namespace ts {
// Modifiers
export function createModifier<T extends Modifier["kind"]>(kind: T) {
export function createModifier<T extends Modifier["kind"]>(kind: T): Token<T> {
return createToken(kind);
}
+159
View File
@@ -0,0 +1,159 @@
/* @internal */
namespace ts {
export interface InspectValueOptions {
readonly fileNameToRequire: string;
}
export const enum ValueKind { Const, Array, FunctionOrClass, Object }
export interface ValueInfoBase {
readonly name: string;
}
export type ValueInfo = ValueInfoSimple | ValueInfoArray | ValueInfoFunctionOrClass | ValueInfoObject;
export interface ValueInfoSimple extends ValueInfoBase {
readonly kind: ValueKind.Const;
readonly typeName: string;
readonly comment?: string | undefined;
}
export interface ValueInfoFunctionOrClass extends ValueInfoBase {
readonly kind: ValueKind.FunctionOrClass;
readonly source: string | number; // For a native function, this is the length.
readonly prototypeMembers: ReadonlyArray<ValueInfo>;
readonly namespaceMembers: ReadonlyArray<ValueInfo>;
}
export interface ValueInfoArray extends ValueInfoBase {
readonly kind: ValueKind.Array;
readonly inner: ValueInfo;
}
export interface ValueInfoObject extends ValueInfoBase {
readonly kind: ValueKind.Object;
readonly members: ReadonlyArray<ValueInfo>;
}
export function inspectModule(fileNameToRequire: string): ValueInfo {
return inspectValue(removeFileExtension(getBaseFileName(fileNameToRequire)), tryRequire(fileNameToRequire));
}
export function inspectValue(name: string, value: unknown): ValueInfo {
return getValueInfo(name, value, getRecurser());
}
type Recurser = <T>(obj: unknown, name: string, cbOk: () => T, cbFail: (isCircularReference: boolean, keyStack: ReadonlyArray<string>) => T) => T;
function getRecurser(): Recurser {
const seen = new Set<unknown>();
const nameStack: string[] = [];
return (obj, name, cbOk, cbFail) => {
if (seen.has(obj) || nameStack.length > 4) {
return cbFail(seen.has(obj), nameStack);
}
seen.add(obj);
nameStack.push(name);
const res = cbOk();
nameStack.pop();
seen.delete(obj);
return res;
};
}
function getValueInfo(name: string, value: unknown, recurser: Recurser): ValueInfo {
return recurser(value, name,
(): ValueInfo => {
if (typeof value === "function") return getFunctionOrClassInfo(value as AnyFunction, name, recurser);
if (typeof value === "object") {
const builtin = getBuiltinType(name, value as object, recurser);
if (builtin !== undefined) return builtin;
const entries = getEntriesOfObject(value as object);
return { kind: ValueKind.Object, name, members: flatMap(entries, ({ key, value }) => getValueInfo(key, value, recurser)) };
}
return { kind: ValueKind.Const, name, typeName: isNullOrUndefined(value) ? "any" : typeof value };
},
(isCircularReference, keyStack) => anyValue(name, ` ${isCircularReference ? "Circular reference" : "Too-deep object hierarchy"} from ${keyStack.join(".")}`));
}
function getFunctionOrClassInfo(fn: AnyFunction, name: string, recurser: Recurser): ValueInfoFunctionOrClass {
const prototypeMembers = getPrototypeMembers(fn, recurser);
const namespaceMembers = flatMap(getEntriesOfObject(fn), ({ key, value }) => getValueInfo(key, value, recurser));
const toString = cast(Function.prototype.toString.call(fn), isString);
const source = stringContains(toString, "{ [native code] }") ? getFunctionLength(fn) : toString;
return { kind: ValueKind.FunctionOrClass, name, source, namespaceMembers, prototypeMembers };
}
const builtins: () => ReadonlyMap<AnyConstructor> = memoize(() => {
const map = createMap<AnyConstructor>();
for (const { key, value } of getEntriesOfObject(global)) {
if (typeof value === "function" && typeof value.prototype === "object" && value !== Object) {
map.set(key, value as AnyConstructor);
}
}
return map;
});
function getBuiltinType(name: string, value: object, recurser: Recurser): ValueInfo | undefined {
return isArray(value)
? { name, kind: ValueKind.Array, inner: value.length && getValueInfo("element", first(value), recurser) || anyValue(name) }
: forEachEntry(builtins(), (builtin, builtinName): ValueInfo | undefined =>
value instanceof builtin ? { kind: ValueKind.Const, name, typeName: builtinName } : undefined);
}
function getPrototypeMembers(fn: AnyFunction, recurser: Recurser): ReadonlyArray<ValueInfo> {
const prototype = fn.prototype as unknown;
// tslint:disable-next-line no-unnecessary-type-assertion (TODO: update LKG and it will really be unnecessary)
return typeof prototype !== "object" || prototype === null ? emptyArray : mapDefined(getEntriesOfObject(prototype as object), ({ key, value }) =>
key === "constructor" ? undefined : getValueInfo(key, value, recurser));
}
const ignoredProperties: ReadonlySet<string> = new Set(["arguments", "caller", "constructor", "eval", "super_"]);
const reservedFunctionProperties: ReadonlySet<string> = new Set(Object.getOwnPropertyNames(noop));
interface ObjectEntry { readonly key: string; readonly value: unknown; }
function getEntriesOfObject(obj: object): ReadonlyArray<ObjectEntry> {
const seen = createMap<true>();
const entries: ObjectEntry[] = [];
let chain = obj;
while (!isNullOrUndefined(chain) && chain !== Object.prototype && chain !== Function.prototype) {
for (const key of Object.getOwnPropertyNames(chain)) {
if (!isJsPrivate(key) &&
!ignoredProperties.has(key) &&
(typeof obj !== "function" || !reservedFunctionProperties.has(key)) &&
// Don't add property from a higher prototype if it already exists in a lower one
addToSeen(seen, key)) {
const value = safeGetPropertyOfObject(chain, key);
// Don't repeat "toString" that matches signature from Object.prototype
if (!(key === "toString" && typeof value === "function" && value.length === 0)) {
entries.push({ key, value });
}
}
}
chain = Object.getPrototypeOf(chain);
}
return entries.sort((e1, e2) => compareStringsCaseSensitive(e1.key, e2.key));
}
function getFunctionLength(fn: AnyFunction): number {
return tryCast(safeGetPropertyOfObject(fn, "length"), isNumber) || 0;
}
function safeGetPropertyOfObject(obj: object, key: string): unknown {
const desc = Object.getOwnPropertyDescriptor(obj, key);
return desc && desc.value;
}
function isNullOrUndefined(value: unknown): value is null | undefined {
return value == null; // tslint:disable-line
}
function anyValue(name: string, comment?: string): ValueInfo {
return { kind: ValueKind.Const, name, typeName: "any", comment };
}
export function isJsPrivate(name: string): boolean {
return name.startsWith("_");
}
function tryRequire(fileNameToRequire: string): unknown {
try {
return require(fileNameToRequire);
}
catch {
return undefined;
}
}
}
+357 -242
View File
@@ -24,6 +24,13 @@ namespace ts {
return withPackageId(/*packageId*/ undefined, r);
}
function removeIgnoredPackageId(r: Resolved | undefined): PathAndExtension | undefined {
if (r) {
Debug.assert(r.packageId === undefined);
return { path: r.path, ext: r.extension };
}
}
/** Result of trying to resolve a module. */
interface Resolved {
path: string;
@@ -67,7 +74,7 @@ namespace ts {
if (!resolved) {
return undefined;
}
Debug.assert(extensionIsTypeScript(resolved.extension));
Debug.assert(extensionIsTS(resolved.extension));
return { fileName: resolved.path, packageId: resolved.packageId };
}
@@ -82,12 +89,14 @@ namespace ts {
host: ModuleResolutionHost;
compilerOptions: CompilerOptions;
traceEnabled: boolean;
failedLookupLocations: Push<string>;
}
/** Just the fields that we use for module resolution. */
interface PackageJsonPathFields {
typings?: string;
types?: string;
typesVersions?: MapLike<MapLike<string[]>>;
main?: string;
}
@@ -96,48 +105,111 @@ namespace ts {
version?: string;
}
/** Reads from "main" or "types"/"typings" depending on `extensions`. */
function tryReadPackageJsonFields(readTypes: boolean, jsonContent: PackageJsonPathFields, baseDirectory: string, state: ModuleResolutionState): string | undefined {
return readTypes ? tryReadFromField("typings") || tryReadFromField("types") : tryReadFromField("main");
type MatchingKeys<TRecord, TMatch, K extends keyof TRecord = keyof TRecord> = K extends (TRecord[K] extends TMatch ? K : never) ? K : never;
function tryReadFromField(fieldName: "typings" | "types" | "main"): string | undefined {
if (!hasProperty(jsonContent, fieldName)) {
if (state.traceEnabled) {
trace(state.host, Diagnostics.package_json_does_not_have_a_0_field, fieldName);
}
return;
}
const fileName = jsonContent[fieldName];
if (!isString(fileName)) {
if (state.traceEnabled) {
trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_string_got_1, fieldName, typeof fileName);
}
return;
}
const path = normalizePath(combinePaths(baseDirectory, fileName));
function readPackageJsonField<TMatch, K extends MatchingKeys<PackageJson, string | undefined>>(jsonContent: PackageJson, fieldName: K, typeOfTag: "string", state: ModuleResolutionState): PackageJson[K] | undefined;
function readPackageJsonField<K extends MatchingKeys<PackageJson, object | undefined>>(jsonContent: PackageJson, fieldName: K, typeOfTag: "object", state: ModuleResolutionState): PackageJson[K] | undefined;
function readPackageJsonField<K extends keyof PackageJson>(jsonContent: PackageJson, fieldName: K, typeOfTag: "string" | "object", state: ModuleResolutionState): PackageJson[K] | undefined {
if (!hasProperty(jsonContent, fieldName)) {
if (state.traceEnabled) {
trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, fieldName, fileName, path);
trace(state.host, Diagnostics.package_json_does_not_have_a_0_field, fieldName);
}
return path;
return;
}
const value = jsonContent[fieldName];
if (typeof value !== typeOfTag || value === null) {
if (state.traceEnabled) {
trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2, fieldName, typeOfTag, value === null ? "null" : typeof value);
}
return;
}
return value;
}
/* @internal */
export function readJson(path: string, host: { readFile(fileName: string): string | undefined }): object {
try {
const jsonText = host.readFile(path);
if (!jsonText) return {};
const result = parseConfigFileTextToJson(path, jsonText);
if (result.error) {
return {};
}
return result.config;
function readPackageJsonPathField<K extends "typings" | "types" | "main">(jsonContent: PackageJson, fieldName: K, baseDirectory: string, state: ModuleResolutionState): PackageJson[K] | undefined {
const fileName = readPackageJsonField(jsonContent, fieldName, "string", state);
if (fileName === undefined) return;
const path = normalizePath(combinePaths(baseDirectory, fileName));
if (state.traceEnabled) {
trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, fieldName, fileName, path);
}
catch (e) {
// gracefully handle if readFile fails or returns not JSON
return {};
return path;
}
function readPackageJsonTypesFields(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) {
return readPackageJsonPathField(jsonContent, "typings", baseDirectory, state)
|| readPackageJsonPathField(jsonContent, "types", baseDirectory, state);
}
function readPackageJsonMainField(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) {
return readPackageJsonPathField(jsonContent, "main", baseDirectory, state);
}
function readPackageJsonTypesVersionsField(jsonContent: PackageJson, state: ModuleResolutionState) {
const typesVersions = readPackageJsonField(jsonContent, "typesVersions", "object", state);
if (typesVersions === undefined) return;
if (state.traceEnabled) {
trace(state.host, Diagnostics.package_json_has_a_typesVersions_field_with_version_specific_path_mappings);
}
return typesVersions;
}
interface VersionPaths {
version: string;
paths: MapLike<string[]>;
}
function readPackageJsonTypesVersionPaths(jsonContent: PackageJson, state: ModuleResolutionState): VersionPaths | undefined {
const typesVersions = readPackageJsonTypesVersionsField(jsonContent, state);
if (typesVersions === undefined) return;
if (state.traceEnabled) {
for (const key in typesVersions) {
if (hasProperty(typesVersions, key) && !VersionRange.tryParse(key)) {
trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_is_not_a_valid_semver_range, key);
}
}
}
const result = getPackageJsonTypesVersionsPaths(typesVersions);
if (!result) {
if (state.traceEnabled) {
trace(state.host, Diagnostics.package_json_does_not_have_a_typesVersions_entry_that_matches_version_0, versionMajorMinor);
}
return;
}
const { version: bestVersionKey, paths: bestVersionPaths } = result;
if (typeof bestVersionPaths !== "object") {
if (state.traceEnabled) {
trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2, `typesVersions['${bestVersionKey}']`, "object", typeof bestVersionPaths);
}
return;
}
return result;
}
let typeScriptVersion: Version | undefined;
/* @internal */
export function getPackageJsonTypesVersionsPaths(typesVersions: MapLike<MapLike<string[]>>) {
if (!typeScriptVersion) typeScriptVersion = new Version(version);
for (const key in typesVersions) {
if (!hasProperty(typesVersions, key)) continue;
const keyRange = VersionRange.tryParse(key);
if (keyRange === undefined) {
continue;
}
// return the first entry whose range matches the current compiler version.
if (keyRange.test(typeScriptVersion)) {
return { version: key, paths: typesVersions[key] };
}
}
}
@@ -188,7 +260,8 @@ namespace ts {
*/
export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost): ResolvedTypeReferenceDirectiveWithFailedLookupLocations {
const traceEnabled = isTraceEnabled(options, host);
const moduleResolutionState: ModuleResolutionState = { compilerOptions: options, host, traceEnabled };
const failedLookupLocations: string[] = [];
const moduleResolutionState: ModuleResolutionState = { compilerOptions: options, host, traceEnabled, failedLookupLocations };
const typeRoots = getEffectiveTypeRoots(options, host);
if (traceEnabled) {
@@ -210,8 +283,6 @@ namespace ts {
}
}
const failedLookupLocations: string[] = [];
let resolved = primaryLookup();
let primary = true;
if (!resolved) {
@@ -247,7 +318,7 @@ namespace ts {
trace(host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, candidateDirectory);
}
return resolvedTypeScriptOnly(
loadNodeModuleFromDirectory(Extensions.DtsOnly, candidate, failedLookupLocations,
loadNodeModuleFromDirectory(Extensions.DtsOnly, candidate,
!directoryExists, moduleResolutionState));
});
}
@@ -266,7 +337,14 @@ namespace ts {
if (traceEnabled) {
trace(host, Diagnostics.Looking_up_in_node_modules_folder_initial_location_0, initialLocationForSecondaryLookup);
}
const result = loadModuleFromNodeModules(Extensions.DtsOnly, typeReferenceDirectiveName, initialLocationForSecondaryLookup, failedLookupLocations, moduleResolutionState, /*cache*/ undefined);
let result: SearchResult<Resolved> | undefined;
if (!isExternalModuleNameRelative(typeReferenceDirectiveName)) {
result = loadModuleFromNearestNodeModulesDirectory(Extensions.DtsOnly, typeReferenceDirectiveName, initialLocationForSecondaryLookup, moduleResolutionState, /*cache*/ undefined);
}
else {
const { path: candidate } = normalizePathAndParts(combinePaths(initialLocationForSecondaryLookup, typeReferenceDirectiveName));
result = toSearchResult(nodeLoadModuleByRelativeName(Extensions.DtsOnly, candidate, /*onlyRecordFailures*/ false, moduleResolutionState, /*considerPackageJson*/ true));
}
const resolvedFile = resolvedTypeScriptOnly(result && result.value);
if (!resolvedFile && traceEnabled) {
trace(host, Diagnostics.Type_reference_directive_0_was_not_resolved, typeReferenceDirectiveName);
@@ -304,14 +382,19 @@ namespace ts {
if (host.directoryExists(root)) {
for (const typeDirectivePath of host.getDirectories(root)) {
const normalized = normalizePath(typeDirectivePath);
const packageJsonPath = pathToPackageJson(combinePaths(root, normalized));
const packageJsonPath = combinePaths(root, normalized, "package.json");
// `types-publisher` sometimes creates packages with `"typings": null` for packages that don't provide their own types.
// See `createNotNeededPackageJSON` in the types-publisher` repo.
// tslint:disable-next-line:no-null-keyword
const isNotNeededPackage = host.fileExists(packageJsonPath) && (readJson(packageJsonPath, host) as PackageJson).typings === null;
if (!isNotNeededPackage) {
// Return just the type directive names
result.push(getBaseFileName(normalized));
const baseFileName = getBaseFileName(normalized);
// At this stage, skip results with leading dot.
if (baseFileName.charCodeAt(0) !== CharacterCodes.dot) {
// Return just the type directive names
result.push(baseFileName);
}
}
}
}
@@ -527,7 +610,7 @@ namespace ts {
* 'typings' entry or file 'index' with some supported extension
* - Classic loader will only try to interpret '/a/b/c' as file.
*/
type ResolutionKindSpecificLoader = (extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState) => Resolved | undefined;
type ResolutionKindSpecificLoader = (extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState) => Resolved | undefined;
/**
* Any module resolution kind can be augmented with optional settings: 'baseUrl', 'paths' and 'rootDirs' - they are used to
@@ -590,18 +673,18 @@ namespace ts {
* entries in 'rootDirs', use them to build absolute path out of (*) and try to resolve module from this location.
*/
function tryLoadModuleUsingOptionalResolutionSettings(extensions: Extensions, moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader,
failedLookupLocations: Push<string>, state: ModuleResolutionState): Resolved | undefined {
state: ModuleResolutionState): Resolved | undefined {
if (!isExternalModuleNameRelative(moduleName)) {
return tryLoadModuleUsingBaseUrl(extensions, moduleName, loader, failedLookupLocations, state);
return tryLoadModuleUsingBaseUrl(extensions, moduleName, loader, state);
}
else {
return tryLoadModuleUsingRootDirs(extensions, moduleName, containingDirectory, loader, failedLookupLocations, state);
return tryLoadModuleUsingRootDirs(extensions, moduleName, containingDirectory, loader, state);
}
}
function tryLoadModuleUsingRootDirs(extensions: Extensions, moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader,
failedLookupLocations: Push<string>, state: ModuleResolutionState): Resolved | undefined {
state: ModuleResolutionState): Resolved | undefined {
if (!state.compilerOptions.rootDirs) {
return undefined;
@@ -646,7 +729,7 @@ namespace ts {
if (state.traceEnabled) {
trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, matchedNormalizedPrefix, candidate);
}
const resolvedFileName = loader(extensions, candidate, failedLookupLocations, !directoryProbablyExists(containingDirectory, state.host), state);
const resolvedFileName = loader(extensions, candidate, !directoryProbablyExists(containingDirectory, state.host), state);
if (resolvedFileName) {
return resolvedFileName;
}
@@ -665,7 +748,7 @@ namespace ts {
trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, rootDir, candidate);
}
const baseDirectory = getDirectoryPath(candidate);
const resolvedFileName = loader(extensions, candidate, failedLookupLocations, !directoryProbablyExists(baseDirectory, state.host), state);
const resolvedFileName = loader(extensions, candidate, !directoryProbablyExists(baseDirectory, state.host), state);
if (resolvedFileName) {
return resolvedFileName;
}
@@ -677,60 +760,28 @@ namespace ts {
return undefined;
}
function tryLoadModuleUsingBaseUrl(extensions: Extensions, moduleName: string, loader: ResolutionKindSpecificLoader, failedLookupLocations: Push<string>, state: ModuleResolutionState): Resolved | undefined {
if (!state.compilerOptions.baseUrl) {
function tryLoadModuleUsingBaseUrl(extensions: Extensions, moduleName: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState): Resolved | undefined {
const { baseUrl, paths } = state.compilerOptions;
if (!baseUrl) {
return undefined;
}
if (state.traceEnabled) {
trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, state.compilerOptions.baseUrl, moduleName);
trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, baseUrl, moduleName);
}
// string is for exact match
let matchedPattern: Pattern | string | undefined;
if (state.compilerOptions.paths) {
if (paths) {
if (state.traceEnabled) {
trace(state.host, Diagnostics.paths_option_is_specified_looking_for_a_pattern_to_match_module_name_0, moduleName);
}
matchedPattern = matchPatternOrExact(getOwnKeys(state.compilerOptions.paths), moduleName);
}
if (matchedPattern) {
const matchedStar = isString(matchedPattern) ? undefined : matchedText(matchedPattern, moduleName);
const matchedPatternText = isString(matchedPattern) ? matchedPattern : patternText(matchedPattern);
if (state.traceEnabled) {
trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText);
const resolved = tryLoadModuleUsingPaths(extensions, moduleName, baseUrl, paths, loader, /*onlyRecordFailures*/ false, state);
if (resolved) {
return resolved.value;
}
return forEach(state.compilerOptions.paths![matchedPatternText], subst => {
const path = matchedStar ? subst.replace("*", matchedStar) : subst;
const candidate = normalizePath(combinePaths(state.compilerOptions.baseUrl!, path));
if (state.traceEnabled) {
trace(state.host, Diagnostics.Trying_substitution_0_candidate_module_location_Colon_1, subst, path);
}
// A path mapping may have an extension, in contrast to an import, which should omit it.
const extension = tryGetExtensionFromPath(candidate);
if (extension !== undefined) {
const path = tryFile(candidate, failedLookupLocations, /*onlyRecordFailures*/ false, state);
if (path !== undefined) {
return noPackageId({ path, ext: extension });
}
}
return loader(extensions, candidate, failedLookupLocations, !directoryProbablyExists(getDirectoryPath(candidate), state.host), state);
});
}
else {
const candidate = normalizePath(combinePaths(state.compilerOptions.baseUrl, moduleName));
if (state.traceEnabled) {
trace(state.host, Diagnostics.Resolving_module_name_0_relative_to_base_url_1_2, moduleName, state.compilerOptions.baseUrl, candidate);
}
return loader(extensions, candidate, failedLookupLocations, !directoryProbablyExists(getDirectoryPath(candidate), state.host), state);
const candidate = normalizePath(combinePaths(baseUrl, moduleName));
if (state.traceEnabled) {
trace(state.host, Diagnostics.Resolving_module_name_0_relative_to_base_url_1_2, moduleName, baseUrl, candidate);
}
}
export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations {
return nodeModuleNameResolverWorker(moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, /*jsOnly*/ false);
return loader(extensions, candidate, !directoryProbablyExists(getDirectoryPath(candidate), state.host), state);
}
/**
@@ -739,20 +790,33 @@ namespace ts {
* Throws an error if the module can't be resolved.
*/
/* @internal */
export function resolveJavaScriptModule(moduleName: string, initialDir: string, host: ModuleResolutionHost): string {
const { resolvedModule, failedLookupLocations } =
nodeModuleNameResolverWorker(moduleName, initialDir, { moduleResolution: ModuleResolutionKind.NodeJs, allowJs: true }, host, /*cache*/ undefined, /*jsOnly*/ true);
export function resolveJSModule(moduleName: string, initialDir: string, host: ModuleResolutionHost): string {
const { resolvedModule, failedLookupLocations } = tryResolveJSModuleWorker(moduleName, initialDir, host);
if (!resolvedModule) {
throw new Error(`Could not resolve JS module '${moduleName}' starting at '${initialDir}'. Looked in: ${failedLookupLocations.join(", ")}`);
}
return resolvedModule.resolvedFileName;
}
/* @internal */
export function tryResolveJSModule(moduleName: string, initialDir: string, host: ModuleResolutionHost): string | undefined {
const { resolvedModule } = tryResolveJSModuleWorker(moduleName, initialDir, host);
return resolvedModule && resolvedModule.resolvedFileName;
}
function tryResolveJSModuleWorker(moduleName: string, initialDir: string, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations {
return nodeModuleNameResolverWorker(moduleName, initialDir, { moduleResolution: ModuleResolutionKind.NodeJs, allowJs: true }, host, /*cache*/ undefined, /*jsOnly*/ true);
}
export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations {
return nodeModuleNameResolverWorker(moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, /*jsOnly*/ false);
}
function nodeModuleNameResolverWorker(moduleName: string, containingDirectory: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache: ModuleResolutionCache | undefined, jsOnly: boolean): ResolvedModuleWithFailedLookupLocations {
const traceEnabled = isTraceEnabled(compilerOptions, host);
const failedLookupLocations: string[] = [];
const state: ModuleResolutionState = { compilerOptions, host, traceEnabled };
const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations };
const result = jsOnly ?
tryResolve(Extensions.JavaScript) :
@@ -766,8 +830,8 @@ namespace ts {
return { resolvedModule: undefined, failedLookupLocations };
function tryResolve(extensions: Extensions): SearchResult<{ resolved: Resolved, isExternalLibraryImport: boolean }> {
const loader: ResolutionKindSpecificLoader = (extensions, candidate, failedLookupLocations, onlyRecordFailures, state) => nodeLoadModuleByRelativeName(extensions, candidate, failedLookupLocations, onlyRecordFailures, state, /*considerPackageJson*/ true);
const resolved = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loader, failedLookupLocations, state);
const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => nodeLoadModuleByRelativeName(extensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ true);
const resolved = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loader, state);
if (resolved) {
return toSearchResult({ resolved, isExternalLibraryImport: stringContains(resolved.path, nodeModulesPathPart) });
}
@@ -776,7 +840,7 @@ namespace ts {
if (traceEnabled) {
trace(host, Diagnostics.Loading_module_0_from_node_modules_folder_target_file_type_1, moduleName, Extensions[extensions]);
}
const resolved = loadModuleFromNodeModules(extensions, moduleName, containingDirectory, failedLookupLocations, state, cache);
const resolved = loadModuleFromNearestNodeModulesDirectory(extensions, moduleName, containingDirectory, state, cache);
if (!resolved) return undefined;
let resolvedValue = resolved.value;
@@ -790,7 +854,7 @@ namespace ts {
}
else {
const { path: candidate, parts } = normalizePathAndParts(combinePaths(containingDirectory, moduleName));
const resolved = nodeLoadModuleByRelativeName(extensions, candidate, failedLookupLocations, /*onlyRecordFailures*/ false, state, /*considerPackageJson*/ true);
const resolved = nodeLoadModuleByRelativeName(extensions, candidate, /*onlyRecordFailures*/ false, state, /*considerPackageJson*/ true);
// Treat explicit "node_modules" import as an external library import.
return resolved && toSearchResult({ resolved, isExternalLibraryImport: contains(parts, "node_modules") });
}
@@ -810,7 +874,7 @@ namespace ts {
return real;
}
function nodeLoadModuleByRelativeName(extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson: boolean): Resolved | undefined {
function nodeLoadModuleByRelativeName(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson: boolean): Resolved | undefined {
if (state.traceEnabled) {
trace(state.host, Diagnostics.Loading_module_as_file_Slash_folder_candidate_module_location_0_target_file_type_1, candidate, Extensions[extensions]);
}
@@ -824,10 +888,11 @@ namespace ts {
onlyRecordFailures = true;
}
}
const resolvedFromFile = loadModuleFromFile(extensions, candidate, failedLookupLocations, onlyRecordFailures, state);
const resolvedFromFile = loadModuleFromFile(extensions, candidate, onlyRecordFailures, state);
if (resolvedFromFile) {
const nm = considerPackageJson ? parseNodeModuleFromPath(resolvedFromFile) : undefined;
const packageId = nm && getPackageJsonInfo(nm.packageDirectory, nm.subModuleName, failedLookupLocations, /*onlyRecordFailures*/ false, state).packageId;
const packageInfo = nm && getPackageJsonInfo(nm.packageDirectory, nm.subModuleName, /*onlyRecordFailures*/ false, state);
const packageId = packageInfo && packageInfo.packageId;
return withPackageId(packageId, resolvedFromFile);
}
}
@@ -840,7 +905,7 @@ namespace ts {
onlyRecordFailures = true;
}
}
return loadNodeModuleFromDirectory(extensions, candidate, failedLookupLocations, onlyRecordFailures, state, considerPackageJson);
return loadNodeModuleFromDirectory(extensions, candidate, onlyRecordFailures, state, considerPackageJson);
}
/*@internal*/
@@ -886,52 +951,46 @@ namespace ts {
if (endsWith(path, ".d.ts")) {
return path;
}
if (endsWith(path, "/index")) {
if (path === "index" || endsWith(path, "/index")) {
return path + ".d.ts";
}
return path + "/index.d.ts";
}
/* @internal */
export function directoryProbablyExists(directoryName: string, host: { directoryExists?: (directoryName: string) => boolean }): boolean {
// if host does not support 'directoryExists' assume that directory will exist
return !host.directoryExists || host.directoryExists(directoryName);
}
function loadModuleFromFileNoPackageId(extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState): Resolved | undefined {
return noPackageId(loadModuleFromFile(extensions, candidate, failedLookupLocations, onlyRecordFailures, state));
function loadModuleFromFileNoPackageId(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): Resolved | undefined {
return noPackageId(loadModuleFromFile(extensions, candidate, onlyRecordFailures, state));
}
/**
* @param {boolean} onlyRecordFailures - if true then function won't try to actually load files but instead record all attempts as failures. This flag is necessary
* in cases when we know upfront that all load attempts will fail (because containing folder does not exists) however we still need to record all failed lookup locations.
*/
function loadModuleFromFile(extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined {
function loadModuleFromFile(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined {
if (extensions === Extensions.Json) {
const extensionLess = tryRemoveExtension(candidate, Extension.Json);
return extensionLess === undefined ? undefined : tryAddingExtensions(extensionLess, extensions, failedLookupLocations, onlyRecordFailures, state);
return extensionLess === undefined ? undefined : tryAddingExtensions(extensionLess, extensions, onlyRecordFailures, state);
}
// First, try adding an extension. An import of "foo" could be matched by a file "foo.ts", or "foo.js" by "foo.js.ts"
const resolvedByAddingExtension = tryAddingExtensions(candidate, extensions, failedLookupLocations, onlyRecordFailures, state);
const resolvedByAddingExtension = tryAddingExtensions(candidate, extensions, onlyRecordFailures, state);
if (resolvedByAddingExtension) {
return resolvedByAddingExtension;
}
// If that didn't work, try stripping a ".js" or ".jsx" extension and replacing it with a TypeScript one;
// e.g. "./foo.js" can be matched by "./foo.ts" or "./foo.d.ts"
if (hasJavaScriptFileExtension(candidate)) {
if (hasJSFileExtension(candidate)) {
const extensionless = removeFileExtension(candidate);
if (state.traceEnabled) {
const extension = candidate.substring(extensionless.length);
trace(state.host, Diagnostics.File_name_0_has_a_1_extension_stripping_it, candidate, extension);
}
return tryAddingExtensions(extensionless, extensions, failedLookupLocations, onlyRecordFailures, state);
return tryAddingExtensions(extensionless, extensions, onlyRecordFailures, state);
}
}
/** Try to return an existing file that adds one of the `extensions` to `candidate`. */
function tryAddingExtensions(candidate: string, extensions: Extensions, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined {
function tryAddingExtensions(candidate: string, extensions: Extensions, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined {
if (!onlyRecordFailures) {
// check if containing folder exists - if it doesn't then just record failures for all supported extensions without disk probing
const directory = getDirectoryPath(candidate);
@@ -952,13 +1011,13 @@ namespace ts {
}
function tryExtension(ext: Extension): PathAndExtension | undefined {
const path = tryFile(candidate + ext, failedLookupLocations, onlyRecordFailures, state);
const path = tryFile(candidate + ext, onlyRecordFailures, state);
return path === undefined ? undefined : { path, ext };
}
}
/** Return the file if it exists. */
function tryFile(fileName: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState): string | undefined {
function tryFile(fileName: string, onlyRecordFailures: boolean, state: ModuleResolutionState): string | undefined {
if (!onlyRecordFailures) {
if (state.host.fileExists(fileName)) {
if (state.traceEnabled) {
@@ -972,48 +1031,40 @@ namespace ts {
}
}
}
failedLookupLocations.push(fileName);
state.failedLookupLocations.push(fileName);
return undefined;
}
function loadNodeModuleFromDirectory(extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson = true) {
const { packageJsonContent, packageId } = considerPackageJson
? getPackageJsonInfo(candidate, "", failedLookupLocations, onlyRecordFailures, state)
: { packageJsonContent: undefined, packageId: undefined };
return withPackageId(packageId, loadNodeModuleFromDirectoryWorker(extensions, candidate, failedLookupLocations, onlyRecordFailures, state, packageJsonContent));
function loadNodeModuleFromDirectory(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson = true) {
const packageInfo = considerPackageJson ? getPackageJsonInfo(candidate, "", onlyRecordFailures, state) : undefined;
const packageId = packageInfo && packageInfo.packageId;
const packageJsonContent = packageInfo && packageInfo.packageJsonContent;
const versionPaths = packageJsonContent && readPackageJsonTypesVersionPaths(packageJsonContent, state);
return withPackageId(packageId, loadNodeModuleFromDirectoryWorker(extensions, candidate, onlyRecordFailures, state, packageJsonContent, versionPaths));
}
function loadNodeModuleFromDirectoryWorker(extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState, packageJsonContent: PackageJsonPathFields | undefined): PathAndExtension | undefined {
const fromPackageJson = packageJsonContent && loadModuleFromPackageJson(packageJsonContent, extensions, candidate, failedLookupLocations, state);
if (fromPackageJson) {
return fromPackageJson;
}
const directoryExists = !onlyRecordFailures && directoryProbablyExists(candidate, state.host);
return loadModuleFromFile(extensions, combinePaths(candidate, "index"), failedLookupLocations, !directoryExists, state);
interface PackageJsonInfo {
packageJsonContent: PackageJsonPathFields | undefined;
packageId: PackageId | undefined;
versionPaths: VersionPaths | undefined;
}
function getPackageJsonInfo(
nodeModuleDirectory: string,
subModuleName: string,
failedLookupLocations: Push<string>,
onlyRecordFailures: boolean,
state: ModuleResolutionState,
): { found: boolean, packageJsonContent: PackageJsonPathFields | undefined, packageId: PackageId | undefined } {
function getPackageJsonInfo(packageDirectory: string, subModuleName: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PackageJsonInfo | undefined {
const { host, traceEnabled } = state;
const directoryExists = !onlyRecordFailures && directoryProbablyExists(nodeModuleDirectory, host);
const packageJsonPath = pathToPackageJson(nodeModuleDirectory);
const directoryExists = !onlyRecordFailures && directoryProbablyExists(packageDirectory, host);
const packageJsonPath = combinePaths(packageDirectory, "package.json");
if (directoryExists && host.fileExists(packageJsonPath)) {
const packageJsonContent = readJson(packageJsonPath, host) as PackageJson;
if (subModuleName === "") { // looking up the root - need to handle types/typings/main redirects for subModuleName
const path = tryReadPackageJsonFields(/*readTypes*/ true, packageJsonContent, nodeModuleDirectory, state);
const path = readPackageJsonTypesFields(packageJsonContent, packageDirectory, state);
if (typeof path === "string") {
subModuleName = addExtensionAndIndex(path.substring(nodeModuleDirectory.length + 1));
subModuleName = addExtensionAndIndex(path.substring(packageDirectory.length + 1));
}
else {
const jsPath = tryReadPackageJsonFields(/*readTypes*/ false, packageJsonContent, nodeModuleDirectory, state);
if (typeof jsPath === "string" && jsPath.length > nodeModuleDirectory.length) {
const potentialSubModule = jsPath.substring(nodeModuleDirectory.length + 1);
subModuleName = (forEach(supportedJavascriptExtensions, extension =>
const jsPath = readPackageJsonMainField(packageJsonContent, packageDirectory, state);
if (typeof jsPath === "string" && jsPath.length > packageDirectory.length) {
const potentialSubModule = jsPath.substring(packageDirectory.length + 1);
subModuleName = (forEach(supportedJSExtensions, extension =>
tryRemoveExtension(potentialSubModule, extension)) || potentialSubModule) + Extension.Dts;
}
else {
@@ -1021,9 +1072,12 @@ namespace ts {
}
}
}
if (!endsWith(subModuleName, Extension.Dts)) {
subModuleName = addExtensionAndIndex(subModuleName);
}
const versionPaths = readPackageJsonTypesVersionPaths(packageJsonContent, state);
const packageId: PackageId | undefined = typeof packageJsonContent.name === "string" && typeof packageJsonContent.version === "string"
? { name: packageJsonContent.name, subModuleName, version: packageJsonContent.version }
: undefined;
@@ -1035,54 +1089,64 @@ namespace ts {
trace(host, Diagnostics.Found_package_json_at_0, packageJsonPath);
}
}
return { found: true, packageJsonContent, packageId };
return { packageJsonContent, packageId, versionPaths };
}
else {
if (directoryExists && traceEnabled) {
trace(host, Diagnostics.File_0_does_not_exist, packageJsonPath);
}
// record package json as one of failed lookup locations - in the future if this file will appear it will invalidate resolution results
failedLookupLocations.push(packageJsonPath);
return { found: false, packageJsonContent: undefined, packageId: undefined };
state.failedLookupLocations.push(packageJsonPath);
}
}
function loadModuleFromPackageJson(jsonContent: PackageJsonPathFields, extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, state: ModuleResolutionState): PathAndExtension | undefined {
let file = tryReadPackageJsonFields(extensions !== Extensions.JavaScript && extensions !== Extensions.Json, jsonContent, candidate, state);
if (!file) {
if (extensions === Extensions.TypeScript) {
function loadNodeModuleFromDirectoryWorker(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, jsonContent: PackageJsonPathFields | undefined, versionPaths: VersionPaths | undefined): PathAndExtension | undefined {
const packageFile = jsonContent && (extensions !== Extensions.JavaScript && extensions !== Extensions.Json
? readPackageJsonTypesFields(jsonContent, candidate, state) ||
// When resolving typescript modules, try resolving using main field as well
file = tryReadPackageJsonFields(/*readTypes*/ false, jsonContent, candidate, state);
if (!file) {
return undefined;
(extensions === Extensions.TypeScript ? readPackageJsonMainField(jsonContent, candidate, state) : undefined)
: readPackageJsonMainField(jsonContent, candidate, state));
const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => {
const fromFile = tryFile(candidate, onlyRecordFailures, state);
if (fromFile) {
const resolved = resolvedIfExtensionMatches(extensions, fromFile);
if (resolved) {
return noPackageId(resolved);
}
if (state.traceEnabled) {
trace(state.host, Diagnostics.File_0_has_an_unsupported_extension_so_skipping_it, fromFile);
}
}
else {
return undefined;
}
}
const onlyRecordFailures = !directoryProbablyExists(getDirectoryPath(file), state.host);
const fromFile = tryFile(file, failedLookupLocations, onlyRecordFailures, state);
if (fromFile) {
const resolved = resolvedIfExtensionMatches(extensions, fromFile);
if (resolved) {
return resolved;
}
// Even if extensions is DtsOnly, we can still look up a .ts file as a result of package.json "types"
const nextExtensions = extensions === Extensions.DtsOnly ? Extensions.TypeScript : extensions;
// Don't do package.json lookup recursively, because Node.js' package lookup doesn't.
return nodeLoadModuleByRelativeName(nextExtensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ false);
};
const onlyRecordFailuresForPackageFile = packageFile ? !directoryProbablyExists(getDirectoryPath(packageFile), state.host) : undefined;
const onlyRecordFailuresForIndex = onlyRecordFailures || !directoryProbablyExists(candidate, state.host);
const indexPath = combinePaths(candidate, "index");
if (versionPaths && (!packageFile || containsPath(candidate, packageFile))) {
const moduleName = getRelativePathFromDirectory(candidate, packageFile || indexPath, /*ignoreCase*/ false);
if (state.traceEnabled) {
trace(state.host, Diagnostics.File_0_has_an_unsupported_extension_so_skipping_it, fromFile);
trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_matches_compiler_version_1_looking_for_a_pattern_to_match_module_name_2, versionPaths.version, version, moduleName);
}
const result = tryLoadModuleUsingPaths(extensions, moduleName, candidate, versionPaths.paths, loader, onlyRecordFailuresForPackageFile || onlyRecordFailuresForIndex, state);
if (result) {
return removeIgnoredPackageId(result.value);
}
}
// Even if extensions is DtsOnly, we can still look up a .ts file as a result of package.json "types"
const nextExtensions = extensions === Extensions.DtsOnly ? Extensions.TypeScript : extensions;
// Don't do package.json lookup recursively, because Node.js' package lookup doesn't.
const result = nodeLoadModuleByRelativeName(nextExtensions, file, failedLookupLocations, onlyRecordFailures, state, /*considerPackageJson*/ false);
if (result) {
// It won't have a `packageId` set, because we disabled `considerPackageJson`.
Debug.assert(result.packageId === undefined);
return { path: result.path, ext: result.extension };
}
// It won't have a `packageId` set, because we disabled `considerPackageJson`.
const packageFileResult = packageFile && removeIgnoredPackageId(loader(extensions, packageFile, onlyRecordFailuresForPackageFile!, state));
if (packageFileResult) return packageFileResult;
return loadModuleFromFile(extensions, indexPath, onlyRecordFailuresForIndex, state);
}
/** Resolve from an arbitrarily specified file. Return `undefined` if it has an unsupported extension. */
@@ -1105,34 +1169,8 @@ namespace ts {
}
}
function pathToPackageJson(directory: string): string {
return combinePaths(directory, "package.json");
}
function loadModuleFromNodeModulesFolder(extensions: Extensions, moduleName: string, nodeModulesFolder: string, nodeModulesFolderExists: boolean, failedLookupLocations: Push<string>, state: ModuleResolutionState): Resolved | undefined {
const candidate = normalizePath(combinePaths(nodeModulesFolder, moduleName));
// First look for a nested package.json, as in `node_modules/foo/bar/package.json`.
let packageJsonContent: PackageJsonPathFields | undefined;
let packageId: PackageId | undefined;
const packageInfo = getPackageJsonInfo(candidate, "", failedLookupLocations, /*onlyRecordFailures*/ !nodeModulesFolderExists, state);
if (packageInfo.found) {
({ packageJsonContent, packageId } = packageInfo);
}
else {
const { packageName, rest } = getPackageName(moduleName);
if (rest !== "") { // If "rest" is empty, we just did this search above.
const packageRootPath = combinePaths(nodeModulesFolder, packageName);
// Don't use a "types" or "main" from here because we're not loading the root, but a subdirectory -- just here for the packageId.
packageId = getPackageJsonInfo(packageRootPath, rest, failedLookupLocations, !nodeModulesFolderExists, state).packageId;
}
}
const pathAndExtension = loadModuleFromFile(extensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state) ||
loadNodeModuleFromDirectoryWorker(extensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state, packageJsonContent);
return withPackageId(packageId, pathAndExtension);
}
/* @internal */
export function getPackageName(moduleName: string): { packageName: string, rest: string } {
export function parsePackageName(moduleName: string): { packageName: string, rest: string } {
let idx = moduleName.indexOf(directorySeparator);
if (moduleName[0] === "@") {
idx = moduleName.indexOf(directorySeparator, idx + 1);
@@ -1140,36 +1178,36 @@ namespace ts {
return idx === -1 ? { packageName: moduleName, rest: "" } : { packageName: moduleName.slice(0, idx), rest: moduleName.slice(idx + 1) };
}
function loadModuleFromNodeModules(extensions: Extensions, moduleName: string, directory: string, failedLookupLocations: Push<string>, state: ModuleResolutionState, cache: NonRelativeModuleNameResolutionCache | undefined): SearchResult<Resolved> {
return loadModuleFromNodeModulesWorker(extensions, moduleName, directory, failedLookupLocations, state, /*typesOnly*/ false, cache);
}
function loadModuleFromNodeModulesAtTypes(moduleName: string, directory: string, failedLookupLocations: Push<string>, state: ModuleResolutionState): SearchResult<Resolved> {
// Extensions parameter here doesn't actually matter, because typesOnly ensures we're just doing @types lookup, which is always DtsOnly.
return loadModuleFromNodeModulesWorker(Extensions.DtsOnly, moduleName, directory, failedLookupLocations, state, /*typesOnly*/ true, /*cache*/ undefined);
function loadModuleFromNearestNodeModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: NonRelativeModuleNameResolutionCache | undefined): SearchResult<Resolved> {
return loadModuleFromNearestNodeModulesDirectoryWorker(extensions, moduleName, directory, state, /*typesScopeOnly*/ false, cache);
}
function loadModuleFromNodeModulesWorker(extensions: Extensions, moduleName: string, directory: string, failedLookupLocations: Push<string>, state: ModuleResolutionState, typesOnly: boolean, cache: NonRelativeModuleNameResolutionCache | undefined): SearchResult<Resolved> {
function loadModuleFromNearestNodeModulesDirectoryTypesScope(moduleName: string, directory: string, state: ModuleResolutionState): SearchResult<Resolved> {
// Extensions parameter here doesn't actually matter, because typesOnly ensures we're just doing @types lookup, which is always DtsOnly.
return loadModuleFromNearestNodeModulesDirectoryWorker(Extensions.DtsOnly, moduleName, directory, state, /*typesScopeOnly*/ true, /*cache*/ undefined);
}
function loadModuleFromNearestNodeModulesDirectoryWorker(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean, cache: NonRelativeModuleNameResolutionCache | undefined): SearchResult<Resolved> {
const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName);
return forEachAncestorDirectory(normalizeSlashes(directory), ancestorDirectory => {
if (getBaseFileName(ancestorDirectory) !== "node_modules") {
const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, ancestorDirectory, state.traceEnabled, state.host, failedLookupLocations);
const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, ancestorDirectory, state);
if (resolutionFromCache) {
return resolutionFromCache;
}
return toSearchResult(loadModuleFromNodeModulesOneLevel(extensions, moduleName, ancestorDirectory, failedLookupLocations, state, typesOnly));
return toSearchResult(loadModuleFromImmediateNodeModulesDirectory(extensions, moduleName, ancestorDirectory, state, typesScopeOnly));
}
});
}
/** Load a module from a single node_modules directory, but not from any ancestors' node_modules directories. */
function loadModuleFromNodeModulesOneLevel(extensions: Extensions, moduleName: string, directory: string, failedLookupLocations: Push<string>, state: ModuleResolutionState, typesOnly = false): Resolved | undefined {
function loadModuleFromImmediateNodeModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean): Resolved | undefined {
const nodeModulesFolder = combinePaths(directory, "node_modules");
const nodeModulesFolderExists = directoryProbablyExists(nodeModulesFolder, state.host);
if (!nodeModulesFolderExists && state.traceEnabled) {
trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, nodeModulesFolder);
}
const packageResult = typesOnly ? undefined : loadModuleFromNodeModulesFolder(extensions, moduleName, nodeModulesFolder, nodeModulesFolderExists, failedLookupLocations, state);
const packageResult = typesScopeOnly ? undefined : loadModuleFromSpecificNodeModulesDirectory(extensions, moduleName, nodeModulesFolder, nodeModulesFolderExists, state);
if (packageResult) {
return packageResult;
}
@@ -1182,7 +1220,84 @@ namespace ts {
}
nodeModulesAtTypesExists = false;
}
return loadModuleFromNodeModulesFolder(Extensions.DtsOnly, mangleScopedPackage(moduleName, state), nodeModulesAtTypes, nodeModulesAtTypesExists, failedLookupLocations, state);
return loadModuleFromSpecificNodeModulesDirectory(Extensions.DtsOnly, mangleScopedPackageNameWithTrace(moduleName, state), nodeModulesAtTypes, nodeModulesAtTypesExists, state);
}
}
function loadModuleFromSpecificNodeModulesDirectory(extensions: Extensions, moduleName: string, nodeModulesDirectory: string, nodeModulesDirectoryExists: boolean, state: ModuleResolutionState): Resolved | undefined {
const candidate = normalizePath(combinePaths(nodeModulesDirectory, moduleName));
// First look for a nested package.json, as in `node_modules/foo/bar/package.json`.
let packageJsonContent: PackageJsonPathFields | undefined;
let packageId: PackageId | undefined;
let versionPaths: VersionPaths | undefined;
const packageInfo = getPackageJsonInfo(candidate, "", !nodeModulesDirectoryExists, state);
if (packageInfo) {
({ packageJsonContent, packageId, versionPaths } = packageInfo);
const fromFile = loadModuleFromFile(extensions, candidate, !nodeModulesDirectoryExists, state);
if (fromFile) {
return noPackageId(fromFile);
}
const fromDirectory = loadNodeModuleFromDirectoryWorker(extensions, candidate, !nodeModulesDirectoryExists, state, packageJsonContent, versionPaths);
return withPackageId(packageId, fromDirectory);
}
const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => {
const pathAndExtension =
loadModuleFromFile(extensions, candidate, onlyRecordFailures, state) ||
loadNodeModuleFromDirectoryWorker(extensions, candidate, onlyRecordFailures, state, packageJsonContent, versionPaths);
return withPackageId(packageId, pathAndExtension);
};
const { packageName, rest } = parsePackageName(moduleName);
if (rest !== "") { // If "rest" is empty, we just did this search above.
const packageDirectory = combinePaths(nodeModulesDirectory, packageName);
// Don't use a "types" or "main" from here because we're not loading the root, but a subdirectory -- just here for the packageId and path mappings.
const packageInfo = getPackageJsonInfo(packageDirectory, rest, !nodeModulesDirectoryExists, state);
if (packageInfo) ({ packageId, versionPaths } = packageInfo);
if (versionPaths) {
if (state.traceEnabled) {
trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_matches_compiler_version_1_looking_for_a_pattern_to_match_module_name_2, versionPaths.version, version, rest);
}
const packageDirectoryExists = nodeModulesDirectoryExists && directoryProbablyExists(packageDirectory, state.host);
const fromPaths = tryLoadModuleUsingPaths(extensions, rest, packageDirectory, versionPaths.paths, loader, !packageDirectoryExists, state);
if (fromPaths) {
return fromPaths.value;
}
}
}
return loader(extensions, candidate, !nodeModulesDirectoryExists, state);
}
function tryLoadModuleUsingPaths(extensions: Extensions, moduleName: string, baseDirectory: string, paths: MapLike<string[]>, loader: ResolutionKindSpecificLoader, onlyRecordFailures: boolean, state: ModuleResolutionState): SearchResult<Resolved> {
const matchedPattern = matchPatternOrExact(getOwnKeys(paths), moduleName);
if (matchedPattern) {
const matchedStar = isString(matchedPattern) ? undefined : matchedText(matchedPattern, moduleName);
const matchedPatternText = isString(matchedPattern) ? matchedPattern : patternText(matchedPattern);
if (state.traceEnabled) {
trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText);
}
const resolved = forEach(paths[matchedPatternText], subst => {
const path = matchedStar ? subst.replace("*", matchedStar) : subst;
const candidate = normalizePath(combinePaths(baseDirectory, path));
if (state.traceEnabled) {
trace(state.host, Diagnostics.Trying_substitution_0_candidate_module_location_Colon_1, subst, path);
}
// A path mapping may have an extension, in contrast to an import, which should omit it.
const extension = tryGetExtensionFromPath(candidate);
if (extension !== undefined) {
const path = tryFile(candidate, onlyRecordFailures, state);
if (path !== undefined) {
return noPackageId({ path, ext: extension });
}
}
return loader(extensions, candidate, onlyRecordFailures || !directoryProbablyExists(getDirectoryPath(candidate), state.host), state);
});
return { value: resolved };
}
}
@@ -1190,8 +1305,8 @@ namespace ts {
const mangledScopedPackageSeparator = "__";
/** For a scoped package, we must look in `@types/foo__bar` instead of `@types/@foo/bar`. */
function mangleScopedPackage(packageName: string, state: ModuleResolutionState): string {
const mangled = getMangledNameForScopedPackage(packageName);
function mangleScopedPackageNameWithTrace(packageName: string, state: ModuleResolutionState): string {
const mangled = mangleScopedPackageName(packageName);
if (state.traceEnabled && mangled !== packageName) {
trace(state.host, Diagnostics.Scoped_package_detected_looking_in_0, mangled);
}
@@ -1200,11 +1315,11 @@ namespace ts {
/* @internal */
export function getTypesPackageName(packageName: string): string {
return `@types/${getMangledNameForScopedPackage(packageName)}`;
return `@types/${mangleScopedPackageName(packageName)}`;
}
/* @internal */
export function getMangledNameForScopedPackage(packageName: string): string {
export function mangleScopedPackageName(packageName: string): string {
if (startsWith(packageName, "@")) {
const replaceSlash = packageName.replace(directorySeparator, mangledScopedPackageSeparator);
if (replaceSlash !== packageName) {
@@ -1215,36 +1330,36 @@ namespace ts {
}
/* @internal */
export function getPackageNameFromAtTypesDirectory(mangledName: string): string {
export function getPackageNameFromTypesPackageName(mangledName: string): string {
const withoutAtTypePrefix = removePrefix(mangledName, "@types/");
if (withoutAtTypePrefix !== mangledName) {
return getUnmangledNameForScopedPackage(withoutAtTypePrefix);
return unmangleScopedPackageName(withoutAtTypePrefix);
}
return mangledName;
}
/* @internal */
export function getUnmangledNameForScopedPackage(typesPackageName: string): string {
export function unmangleScopedPackageName(typesPackageName: string): string {
return stringContains(typesPackageName, mangledScopedPackageSeparator) ?
"@" + typesPackageName.replace(mangledScopedPackageSeparator, directorySeparator) :
typesPackageName;
}
function tryFindNonRelativeModuleNameInCache(cache: PerModuleNameCache | undefined, moduleName: string, containingDirectory: string, traceEnabled: boolean, host: ModuleResolutionHost, failedLookupLocations: Push<string>): SearchResult<Resolved> {
function tryFindNonRelativeModuleNameInCache(cache: PerModuleNameCache | undefined, moduleName: string, containingDirectory: string, state: ModuleResolutionState): SearchResult<Resolved> {
const result = cache && cache.get(containingDirectory);
if (result) {
if (traceEnabled) {
trace(host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory);
if (state.traceEnabled) {
trace(state.host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory);
}
failedLookupLocations.push(...result.failedLookupLocations);
state.failedLookupLocations.push(...result.failedLookupLocations);
return { value: result.resolvedModule && { path: result.resolvedModule.resolvedFileName, originalPath: result.resolvedModule.originalPath || true, extension: result.resolvedModule.extension, packageId: result.resolvedModule.packageId } };
}
}
export function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: NonRelativeModuleNameResolutionCache): ResolvedModuleWithFailedLookupLocations {
const traceEnabled = isTraceEnabled(compilerOptions, host);
const state: ModuleResolutionState = { compilerOptions, host, traceEnabled };
const failedLookupLocations: string[] = [];
const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations };
const containingDirectory = getDirectoryPath(containingFile);
const resolved = tryResolve(Extensions.TypeScript) || tryResolve(Extensions.JavaScript);
@@ -1252,7 +1367,7 @@ namespace ts {
return createResolvedModuleWithFailedLookupLocations(resolved && resolved.value, /*isExternalLibraryImport*/ false, failedLookupLocations);
function tryResolve(extensions: Extensions): SearchResult<Resolved> {
const resolvedUsingSettings = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loadModuleFromFileNoPackageId, failedLookupLocations, state);
const resolvedUsingSettings = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loadModuleFromFileNoPackageId, state);
if (resolvedUsingSettings) {
return { value: resolvedUsingSettings };
}
@@ -1261,24 +1376,24 @@ namespace ts {
const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName);
// Climb up parent directories looking for a module.
const resolved = forEachAncestorDirectory(containingDirectory, directory => {
const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, directory, traceEnabled, host, failedLookupLocations);
const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, directory, state);
if (resolutionFromCache) {
return resolutionFromCache;
}
const searchName = normalizePath(combinePaths(directory, moduleName));
return toSearchResult(loadModuleFromFileNoPackageId(extensions, searchName, failedLookupLocations, /*onlyRecordFailures*/ false, state));
return toSearchResult(loadModuleFromFileNoPackageId(extensions, searchName, /*onlyRecordFailures*/ false, state));
});
if (resolved) {
return resolved;
}
if (extensions === Extensions.TypeScript) {
// If we didn't find the file normally, look it up in @types.
return loadModuleFromNodeModulesAtTypes(moduleName, containingDirectory, failedLookupLocations, state);
return loadModuleFromNearestNodeModulesDirectoryTypesScope(moduleName, containingDirectory, state);
}
}
else {
const candidate = normalizePath(combinePaths(containingDirectory, moduleName));
return toSearchResult(loadModuleFromFileNoPackageId(extensions, candidate, failedLookupLocations, /*onlyRecordFailures*/ false, state));
return toSearchResult(loadModuleFromFileNoPackageId(extensions, candidate, /*onlyRecordFailures*/ false, state));
}
}
}
@@ -1293,9 +1408,9 @@ namespace ts {
if (traceEnabled) {
trace(host, Diagnostics.Auto_discovery_for_typings_is_enabled_in_project_0_Running_extra_resolution_pass_for_module_1_using_cache_location_2, projectName, moduleName, globalCache);
}
const state: ModuleResolutionState = { compilerOptions, host, traceEnabled };
const failedLookupLocations: string[] = [];
const resolved = loadModuleFromNodeModulesOneLevel(Extensions.DtsOnly, moduleName, globalCache, failedLookupLocations, state);
const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations };
const resolved = loadModuleFromImmediateNodeModulesDirectory(Extensions.DtsOnly, moduleName, globalCache, state, /*typesScopeOnly*/ false);
return createResolvedModuleWithFailedLookupLocations(resolved, /*isExternalLibraryImport*/ true, failedLookupLocations);
}
+38 -18
View File
@@ -30,7 +30,7 @@ namespace ts.moduleSpecifiers {
function getPreferencesForUpdate(compilerOptions: CompilerOptions, oldImportSpecifier: string): Preferences {
return {
relativePreference: isExternalModuleNameRelative(oldImportSpecifier) ? RelativePreference.Relative : RelativePreference.NonRelative,
ending: hasJavaScriptOrJsonFileExtension(oldImportSpecifier) ? Ending.JsExtension
ending: hasJSOrJsonFileExtension(oldImportSpecifier) ? Ending.JsExtension
: getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeJs || endsWith(oldImportSpecifier, "index") ? Ending.Index : Ending.Minimal,
};
}
@@ -148,7 +148,7 @@ namespace ts.moduleSpecifiers {
}
function usesJsExtensionOnImports({ imports }: SourceFile): boolean {
return firstDefined(imports, ({ text }) => pathIsRelative(text) ? hasJavaScriptOrJsonFileExtension(text) : undefined) || false;
return firstDefined(imports, ({ text }) => pathIsRelative(text) ? hasJSOrJsonFileExtension(text) : undefined) || false;
}
function stringsEqual(a: string, b: string, getCanonicalFileName: GetCanonicalFileName): boolean {
@@ -235,7 +235,8 @@ namespace ts.moduleSpecifiers {
const suffix = pattern.substr(indexOfStar + 1);
if (relativeToBaseUrl.length >= prefix.length + suffix.length &&
startsWith(relativeToBaseUrl, prefix) &&
endsWith(relativeToBaseUrl, suffix)) {
endsWith(relativeToBaseUrl, suffix) ||
!suffix && relativeToBaseUrl === removeTrailingDirectorySeparator(prefix)) {
const matchedStar = relativeToBaseUrl.substr(prefix.length, relativeToBaseUrl.length - suffix.length);
return key.replace("*", matchedStar);
}
@@ -259,11 +260,34 @@ namespace ts.moduleSpecifiers {
}
function tryGetModuleNameAsNodeModule(moduleFileName: string, { getCanonicalFileName, sourceDirectory }: Info, host: ModuleSpecifierResolutionHost, options: CompilerOptions): string | undefined {
if (!host.fileExists || !host.readFile) {
return undefined;
}
const parts: NodeModulePathParts = getNodeModulePathParts(moduleFileName)!;
if (!parts) {
return undefined;
}
const packageRootPath = moduleFileName.substring(0, parts.packageRootIndex);
const packageJsonPath = combinePaths(packageRootPath, "package.json");
const packageJsonContent = host.fileExists(packageJsonPath)
? JSON.parse(host.readFile(packageJsonPath)!)
: undefined;
const versionPaths = packageJsonContent && packageJsonContent.typesVersions
? getPackageJsonTypesVersionsPaths(packageJsonContent.typesVersions)
: undefined;
if (versionPaths) {
const subModuleName = moduleFileName.slice(parts.packageRootIndex + 1);
const fromPaths = tryGetModuleNameFromPaths(
removeFileExtension(subModuleName),
removeExtensionAndIndexPostFix(subModuleName, Ending.Minimal, options),
versionPaths.paths
);
if (fromPaths !== undefined) {
moduleFileName = combinePaths(moduleFileName.slice(0, parts.packageRootIndex), fromPaths);
}
}
// Simplify the full file path to something that can be resolved by Node.
// If the module could be imported by a directory name, use that directory's name
@@ -274,23 +298,18 @@ namespace ts.moduleSpecifiers {
// If the module was found in @types, get the actual Node package name
const nodeModulesDirectoryName = moduleSpecifier.substring(parts.topLevelPackageNameIndex + 1);
const packageName = getPackageNameFromAtTypesDirectory(nodeModulesDirectoryName);
const packageName = getPackageNameFromTypesPackageName(nodeModulesDirectoryName);
// For classic resolution, only allow importing from node_modules/@types, not other node_modules
return getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeJs && packageName === nodeModulesDirectoryName ? undefined : packageName;
function getDirectoryOrExtensionlessFileName(path: string): string {
// If the file is the main module, it can be imported by the package name
const packageRootPath = path.substring(0, parts.packageRootIndex);
const packageJsonPath = combinePaths(packageRootPath, "package.json");
if (host.fileExists!(packageJsonPath)) { // TODO: GH#18217
const packageJsonContent = JSON.parse(host.readFile!(packageJsonPath)!);
if (packageJsonContent) {
const mainFileRelative = packageJsonContent.typings || packageJsonContent.types || packageJsonContent.main;
if (mainFileRelative) {
const mainExportFile = toPath(mainFileRelative, packageRootPath, getCanonicalFileName);
if (removeFileExtension(mainExportFile) === removeFileExtension(getCanonicalFileName(path))) {
return packageRootPath;
}
if (packageJsonContent) {
const mainFileRelative = packageJsonContent.typings || packageJsonContent.types || packageJsonContent.main;
if (mainFileRelative) {
const mainExportFile = toPath(mainFileRelative, packageRootPath, getCanonicalFileName);
if (removeFileExtension(mainExportFile) === removeFileExtension(getCanonicalFileName(path))) {
return packageRootPath;
}
}
}
@@ -309,11 +328,12 @@ namespace ts.moduleSpecifiers {
}
function tryGetAnyFileFromPath(host: ModuleSpecifierResolutionHost, path: string) {
if (!host.fileExists) return;
// We check all js, `node` and `json` extensions in addition to TS, since node module resolution would also choose those over the directory
const extensions = getSupportedExtensions({ allowJs: true }, [{ extension: "node", isMixedContent: false }, { extension: "json", isMixedContent: false, scriptKind: ScriptKind.JSON }]);
for (const e of extensions) {
const fullPath = path + e;
if (host.fileExists!(fullPath)) { // TODO: GH#18217
if (host.fileExists(fullPath)) {
return fullPath;
}
}
@@ -399,13 +419,13 @@ namespace ts.moduleSpecifiers {
case Ending.Index:
return noExtension;
case Ending.JsExtension:
return noExtension + getJavaScriptExtensionForFile(fileName, options);
return noExtension + getJSExtensionForFile(fileName, options);
default:
return Debug.assertNever(ending);
}
}
function getJavaScriptExtensionForFile(fileName: string, options: CompilerOptions): Extension {
function getJSExtensionForFile(fileName: string, options: CompilerOptions): Extension {
const ext = extensionFromPath(fileName);
switch (ext) {
case Extension.Ts:
+39 -39
View File
@@ -1482,7 +1482,15 @@ namespace ts {
// which would be a candidate for improved error reporting.
return token() === SyntaxKind.OpenBracketToken || isLiteralPropertyName();
case ParsingContext.ObjectLiteralMembers:
return token() === SyntaxKind.OpenBracketToken || token() === SyntaxKind.AsteriskToken || token() === SyntaxKind.DotDotDotToken || isLiteralPropertyName();
switch (token()) {
case SyntaxKind.OpenBracketToken:
case SyntaxKind.AsteriskToken:
case SyntaxKind.DotDotDotToken:
case SyntaxKind.DotToken: // Not an object literal member, but don't want to close the object (see `tests/cases/fourslash/completionsDotInObjectLiteral.ts`)
return true;
default:
return isLiteralPropertyName();
}
case ParsingContext.RestProperties:
return isLiteralPropertyName();
case ParsingContext.ObjectBindingElements:
@@ -6308,7 +6316,7 @@ namespace ts {
// Parses out a JSDoc type expression.
export function parseJSDocTypeExpression(mayOmitBraces?: boolean): JSDocTypeExpression {
const result = <JSDocTypeExpression>createNode(SyntaxKind.JSDocTypeExpression, scanner.getTokenPos());
const result = <JSDocTypeExpression>createNode(SyntaxKind.JSDocTypeExpression);
const hasBrace = (mayOmitBraces ? parseOptional : parseExpected)(SyntaxKind.OpenBraceToken);
result.type = doInsideOfContext(NodeFlags.JSDoc, parseJSDocType);
@@ -6441,13 +6449,6 @@ namespace ts {
indent += asterisk.length;
}
break;
case SyntaxKind.Identifier:
// Anything else is doc comment text. We just save it. Because it
// wasn't a tag, we can no longer parse a tag on this line until we hit the next
// line break.
pushComment(scanner.getTokenText());
state = JSDocState.SavingComments;
break;
case SyntaxKind.WhitespaceTrivia:
// only collect whitespace if we're already saving comments or have just crossed the comment indent margin
const whitespace = scanner.getTokenText();
@@ -6462,7 +6463,9 @@ namespace ts {
case SyntaxKind.EndOfFileToken:
break loop;
default:
// anything other than whitespace or asterisk at the beginning of the line starts the comment text
// Anything else is doc comment text. We just save it. Because it
// wasn't a tag, we can no longer parse a tag on this line until we hit the next
// line break.
state = JSDocState.SavingComments;
pushComment(scanner.getTokenText());
break;
@@ -6542,7 +6545,7 @@ namespace ts {
atToken.end = scanner.getTextPos();
nextJSDocToken();
const tagName = parseJSDocIdentifierName();
const tagName = parseJSDocIdentifierName(/*message*/ undefined);
skipWhitespaceOrAsterisk();
let tag: JSDocTag | undefined;
@@ -6724,7 +6727,7 @@ namespace ts {
}
}
function parseParameterOrPropertyTag(atToken: AtToken, tagName: Identifier, target: PropertyLikeParse, indent: number | undefined): JSDocParameterTag | JSDocPropertyTag {
function parseParameterOrPropertyTag(atToken: AtToken, tagName: Identifier, target: PropertyLikeParse, indent: number): JSDocParameterTag | JSDocPropertyTag {
let typeExpression = tryParseTypeExpression();
let isNameFirst = !typeExpression;
skipWhitespaceOrAsterisk();
@@ -6739,9 +6742,8 @@ namespace ts {
const result = target === PropertyLikeParse.Property ?
<JSDocPropertyTag>createNode(SyntaxKind.JSDocPropertyTag, atToken.pos) :
<JSDocParameterTag>createNode(SyntaxKind.JSDocParameterTag, atToken.pos);
let comment: string | undefined;
if (indent !== undefined) comment = parseTagComments(indent + scanner.getStartPos() - atToken.pos);
const nestedTypeLiteral = target !== PropertyLikeParse.CallbackParameter && parseNestedTypeLiteral(typeExpression, name, target);
const comment = parseTagComments(indent + scanner.getStartPos() - atToken.pos);
const nestedTypeLiteral = target !== PropertyLikeParse.CallbackParameter && parseNestedTypeLiteral(typeExpression, name, target, indent);
if (nestedTypeLiteral) {
typeExpression = nestedTypeLiteral;
isNameFirst = true;
@@ -6756,14 +6758,14 @@ namespace ts {
return finishNode(result);
}
function parseNestedTypeLiteral(typeExpression: JSDocTypeExpression | undefined, name: EntityName, target: PropertyLikeParse) {
function parseNestedTypeLiteral(typeExpression: JSDocTypeExpression | undefined, name: EntityName, target: PropertyLikeParse, indent: number) {
if (typeExpression && isObjectOrObjectArrayTypeReference(typeExpression.type)) {
const typeLiteralExpression = <JSDocTypeExpression>createNode(SyntaxKind.JSDocTypeExpression, scanner.getTokenPos());
let child: JSDocPropertyLikeTag | JSDocTypeTag | false;
let jsdocTypeLiteral: JSDocTypeLiteral;
const start = scanner.getStartPos();
let children: JSDocPropertyLikeTag[] | undefined;
while (child = tryParse(() => parseChildParameterOrPropertyTag(target, name))) {
while (child = tryParse(() => parseChildParameterOrPropertyTag(target, indent, name))) {
if (child.kind === SyntaxKind.JSDocParameterTag || child.kind === SyntaxKind.JSDocPropertyTag) {
children = append(children, child);
}
@@ -6879,7 +6881,7 @@ namespace ts {
let jsdocTypeLiteral: JSDocTypeLiteral | undefined;
let childTypeTag: JSDocTypeTag | undefined;
const start = atToken.pos;
while (child = tryParse(() => parseChildPropertyTag())) {
while (child = tryParse(() => parseChildPropertyTag(indent))) {
if (!jsdocTypeLiteral) {
jsdocTypeLiteral = <JSDocTypeLiteral>createNode(SyntaxKind.JSDocTypeLiteral, start);
}
@@ -6945,7 +6947,7 @@ namespace ts {
const start = scanner.getStartPos();
const jsdocSignature = createNode(SyntaxKind.JSDocSignature, start) as JSDocSignature;
jsdocSignature.parameters = [];
while (child = tryParse(() => parseChildParameterOrPropertyTag(PropertyLikeParse.CallbackParameter) as JSDocParameterTag)) {
while (child = tryParse(() => parseChildParameterOrPropertyTag(PropertyLikeParse.CallbackParameter, indent) as JSDocParameterTag)) {
jsdocSignature.parameters = append(jsdocSignature.parameters as MutableNodeArray<JSDocParameterTag>, child);
}
const returnTag = tryParse(() => {
@@ -6988,18 +6990,18 @@ namespace ts {
return a.escapedText === b.escapedText;
}
function parseChildPropertyTag() {
return parseChildParameterOrPropertyTag(PropertyLikeParse.Property) as JSDocTypeTag | JSDocPropertyTag | false;
function parseChildPropertyTag(indent: number) {
return parseChildParameterOrPropertyTag(PropertyLikeParse.Property, indent) as JSDocTypeTag | JSDocPropertyTag | false;
}
function parseChildParameterOrPropertyTag(target: PropertyLikeParse, name?: EntityName): JSDocTypeTag | JSDocPropertyTag | JSDocParameterTag | false {
function parseChildParameterOrPropertyTag(target: PropertyLikeParse, indent: number, name?: EntityName): JSDocTypeTag | JSDocPropertyTag | JSDocParameterTag | false {
let canParseTag = true;
let seenAsterisk = false;
while (true) {
switch (nextJSDocToken()) {
case SyntaxKind.AtToken:
if (canParseTag) {
const child = tryParseChildTag(target);
const child = tryParseChildTag(target, indent);
if (child && (child.kind === SyntaxKind.JSDocParameterTag || child.kind === SyntaxKind.JSDocPropertyTag) &&
target !== PropertyLikeParse.CallbackParameter &&
name && (ts.isIdentifier(child.name) || !escapedTextsEqual(name, child.name.left))) {
@@ -7028,7 +7030,7 @@ namespace ts {
}
}
function tryParseChildTag(target: PropertyLikeParse): JSDocTypeTag | JSDocPropertyTag | JSDocParameterTag | false {
function tryParseChildTag(target: PropertyLikeParse, indent: number): JSDocTypeTag | JSDocPropertyTag | JSDocParameterTag | false {
Debug.assert(token() === SyntaxKind.AtToken);
const atToken = <AtToken>createNode(SyntaxKind.AtToken);
atToken.end = scanner.getTextPos();
@@ -7055,9 +7057,7 @@ namespace ts {
if (!(target & t)) {
return false;
}
const tag = parseParameterOrPropertyTag(atToken, tagName, target, /*indent*/ undefined);
tag.comment = parseTagComments(tag.end - tag.pos);
return tag;
return parseParameterOrPropertyTag(atToken, tagName, target, indent);
}
function parseTemplateTag(atToken: AtToken, tagName: Identifier): JSDocTemplateTag {
@@ -7727,7 +7727,7 @@ namespace ts {
/*@internal*/
export function processCommentPragmas(context: PragmaContext, sourceText: string): void {
const triviaScanner = createScanner(context.languageVersion, /*skipTrivia*/ false, LanguageVariant.Standard, sourceText);
const pragmas: PragmaPsuedoMapEntry[] = [];
const pragmas: PragmaPseudoMapEntry[] = [];
// Keep scanning all the leading trivia in the file until we get to something that
// isn't trivia. Any single line comment will be analyzed to see if it is a
@@ -7782,7 +7782,7 @@ namespace ts {
const referencedFiles = context.referencedFiles;
const typeReferenceDirectives = context.typeReferenceDirectives;
const libReferenceDirectives = context.libReferenceDirectives;
forEach(toArray(entryOrList), (arg: PragmaPsuedoMap["reference"]) => {
forEach(toArray(entryOrList), (arg: PragmaPseudoMap["reference"]) => {
// TODO: GH#18217
if (arg!.arguments["no-default-lib"]) {
context.hasNoDefaultLib = true;
@@ -7805,7 +7805,7 @@ namespace ts {
case "amd-dependency": {
context.amdDependencies = map(
toArray(entryOrList),
(x: PragmaPsuedoMap["amd-dependency"]) => ({ name: x!.arguments.name!, path: x!.arguments.path })); // TODO: GH#18217
(x: PragmaPseudoMap["amd-dependency"]) => ({ name: x!.arguments.name!, path: x!.arguments.path })); // TODO: GH#18217
break;
}
case "amd-module": {
@@ -7815,11 +7815,11 @@ namespace ts {
// TODO: It's probably fine to issue this diagnostic on all instances of the pragma
reportDiagnostic(entry!.range.pos, entry!.range.end - entry!.range.pos, Diagnostics.An_AMD_module_cannot_have_multiple_name_assignments);
}
context.moduleName = (entry as PragmaPsuedoMap["amd-module"])!.arguments.name;
context.moduleName = (entry as PragmaPseudoMap["amd-module"])!.arguments.name;
}
}
else {
context.moduleName = (entryOrList as PragmaPsuedoMap["amd-module"])!.arguments.name;
context.moduleName = (entryOrList as PragmaPseudoMap["amd-module"])!.arguments.name;
}
break;
}
@@ -7855,10 +7855,10 @@ namespace ts {
const tripleSlashXMLCommentStartRegEx = /^\/\/\/\s*<(\S+)\s.*?\/>/im;
const singleLinePragmaRegEx = /^\/\/\/?\s*@(\S+)\s*(.*)\s*$/im;
function extractPragmas(pragmas: PragmaPsuedoMapEntry[], range: CommentRange, text: string) {
function extractPragmas(pragmas: PragmaPseudoMapEntry[], range: CommentRange, text: string) {
const tripleSlash = range.kind === SyntaxKind.SingleLineCommentTrivia && tripleSlashXMLCommentStartRegEx.exec(text);
if (tripleSlash) {
const name = tripleSlash[1].toLowerCase() as keyof PragmaPsuedoMap; // Technically unsafe cast, but we do it so the below check to make it safe typechecks
const name = tripleSlash[1].toLowerCase() as keyof PragmaPseudoMap; // Technically unsafe cast, but we do it so the below check to make it safe typechecks
const pragma = commentPragmas[name] as PragmaDefinition;
if (!pragma || !(pragma.kind! & PragmaKindFlags.TripleSlashXML)) {
return;
@@ -7885,10 +7885,10 @@ namespace ts {
}
}
}
pragmas.push({ name, args: { arguments: argument, range } } as PragmaPsuedoMapEntry);
pragmas.push({ name, args: { arguments: argument, range } } as PragmaPseudoMapEntry);
}
else {
pragmas.push({ name, args: { arguments: {}, range } } as PragmaPsuedoMapEntry);
pragmas.push({ name, args: { arguments: {}, range } } as PragmaPseudoMapEntry);
}
return;
}
@@ -7907,9 +7907,9 @@ namespace ts {
}
}
function addPragmaForMatch(pragmas: PragmaPsuedoMapEntry[], range: CommentRange, kind: PragmaKindFlags, match: RegExpExecArray) {
function addPragmaForMatch(pragmas: PragmaPseudoMapEntry[], range: CommentRange, kind: PragmaKindFlags, match: RegExpExecArray) {
if (!match) return;
const name = match[1].toLowerCase() as keyof PragmaPsuedoMap; // Technically unsafe cast, but we do it so they below check to make it safe typechecks
const name = match[1].toLowerCase() as keyof PragmaPseudoMap; // Technically unsafe cast, but we do it so they below check to make it safe typechecks
const pragma = commentPragmas[name] as PragmaDefinition;
if (!pragma || !(pragma.kind! & kind)) {
return;
@@ -7917,7 +7917,7 @@ namespace ts {
const args = match[2]; // Split on spaces and match up positionally with definition
const argument = getNamedPragmaArguments(pragma, args);
if (argument === "fail") return; // Missing required argument, fail to parse it
pragmas.push({ name, args: { arguments: argument, range } } as PragmaPsuedoMapEntry);
pragmas.push({ name, args: { arguments: argument, range } } as PragmaPseudoMapEntry);
return;
}
+152 -100
View File
@@ -207,7 +207,7 @@ namespace ts {
...program.getSemanticDiagnostics(sourceFile, cancellationToken)
];
if (program.getCompilerOptions().declaration) {
if (getEmitDeclarations(program.getCompilerOptions())) {
addRange(diagnostics, program.getDeclarationDiagnostics(sourceFile, cancellationToken));
}
@@ -250,7 +250,7 @@ namespace ts {
Blue = "\u001b[94m",
Cyan = "\u001b[96m"
}
const gutterStyleSequence = "\u001b[30;47m";
const gutterStyleSequence = "\u001b[7m";
const gutterSeparator = " ";
const resetEscapeSequence = "\u001b[0m";
const ellipsis = "...";
@@ -442,6 +442,7 @@ namespace ts {
fileExists: (fileName: string) => boolean,
hasInvalidatedResolution: HasInvalidatedResolution,
hasChangedAutomaticTypeDirectiveNames: boolean,
projectReferences: ReadonlyArray<ProjectReference> | undefined
): boolean {
// If we haven't created a program yet or have changed automatic type directives, then it is not up-to-date
if (!program || hasChangedAutomaticTypeDirectiveNames) {
@@ -453,6 +454,11 @@ namespace ts {
return false;
}
// If project references dont match
if (!arrayIsEqualTo(program.getProjectReferences(), projectReferences, projectReferenceUptoDate)) {
return false;
}
// If any file is not up-to-date, then the whole program is not up-to-date
if (program.getSourceFiles().some(sourceFileNotUptoDate)) {
return false;
@@ -477,9 +483,27 @@ namespace ts {
return true;
function sourceFileNotUptoDate(sourceFile: SourceFile): boolean {
return sourceFile.version !== getSourceVersion(sourceFile.path) ||
hasInvalidatedResolution(sourceFile.path);
function sourceFileNotUptoDate(sourceFile: SourceFile) {
return !sourceFileVersionUptoDate(sourceFile) ||
hasInvalidatedResolution(sourceFile.resolvedPath);
}
function sourceFileVersionUptoDate(sourceFile: SourceFile) {
return sourceFile.version === getSourceVersion(sourceFile.resolvedPath);
}
function projectReferenceUptoDate(oldRef: ProjectReference, newRef: ProjectReference, index: number) {
if (!projectReferenceIsEqualTo(oldRef, newRef)) {
return false;
}
const oldResolvedRef = program!.getResolvedProjectReferences()![index];
if (oldResolvedRef) {
// If sourceFile for the oldResolvedRef existed, check the version for uptodate
return sourceFileVersionUptoDate(oldResolvedRef.sourceFile);
}
// In old program, not able to resolve project reference path,
// so if config file doesnt exist, it is uptodate.
return !fileExists(resolveProjectReferencePath(oldRef));
}
}
@@ -490,23 +514,15 @@ namespace ts {
}
/**
* Determined if source file needs to be re-created even if its text hasn't changed
* Determine if source file needs to be re-created even if its text hasn't changed
*/
function shouldProgramCreateNewSourceFiles(program: Program | undefined, newOptions: CompilerOptions) {
// If any of these options change, we can't reuse old source file even if version match
// The change in options like these could result in change in syntax tree change
const oldOptions = program && program.getCompilerOptions();
return oldOptions && (
oldOptions.target !== newOptions.target ||
oldOptions.module !== newOptions.module ||
oldOptions.moduleResolution !== newOptions.moduleResolution ||
oldOptions.noResolve !== newOptions.noResolve ||
oldOptions.jsx !== newOptions.jsx ||
oldOptions.allowJs !== newOptions.allowJs ||
oldOptions.disableSizeLimit !== newOptions.disableSizeLimit ||
oldOptions.baseUrl !== newOptions.baseUrl ||
!equalOwnProperties(oldOptions.paths, newOptions.paths)
);
function shouldProgramCreateNewSourceFiles(program: Program | undefined, newOptions: CompilerOptions): boolean {
if (!program) return false;
// If any compiler options change, we can't reuse old source file even if version match
// The change in options like these could result in change in syntax tree or `sourceFile.bindDiagnostics`.
const oldOptions = program.getCompilerOptions();
return !!sourceFileAffectingCompilerOptions.some(option =>
!isJsonEqual(getCompilerOptionValue(oldOptions, option), getCompilerOptionValue(newOptions, option)));
}
function createCreateProgramOptions(rootNames: ReadonlyArray<string>, options: CompilerOptions, host?: CompilerHost, oldProgram?: Program, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>): CreateProgramOptions {
@@ -594,7 +610,7 @@ namespace ts {
const programDiagnostics = createDiagnosticCollection();
const currentDirectory = host.getCurrentDirectory();
const supportedExtensions = getSupportedExtensions(options);
const supportedExtensionsWithJsonIfResolveJsonModule = options.resolveJsonModule ? [...supportedExtensions, Extension.Json] : undefined;
const supportedExtensionsWithJsonIfResolveJsonModule = getSuppoertedExtensionsWithJsonIfResolveJsonModule(options, supportedExtensions);
// Map storing if there is emit blocking diagnostics for given input
const hasEmitBlockingDiagnostics = createMap<boolean>();
@@ -647,7 +663,7 @@ namespace ts {
// A parallel array to projectReferences storing the results of reading in the referenced tsconfig files
let resolvedProjectReferences: (ResolvedProjectReference | undefined)[] | undefined = projectReferences ? [] : undefined;
const projectReferenceRedirects: Map<string> = createMap();
let projectReferenceRedirects: ParsedCommandLine[] | undefined;
const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options);
const structuralIsReused = tryReuseStructureFromOldProgram();
@@ -660,11 +676,12 @@ namespace ts {
const parsedRef = parseProjectReferenceConfigFile(ref);
resolvedProjectReferences!.push(parsedRef);
if (parsedRef) {
if (parsedRef.commandLine.options.outFile) {
const dtsOutfile = changeExtension(parsedRef.commandLine.options.outFile, ".d.ts");
const out = parsedRef.commandLine.options.outFile || parsedRef.commandLine.options.out;
if (out) {
const dtsOutfile = changeExtension(out, ".d.ts");
processSourceFile(dtsOutfile, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined);
}
addProjectReferenceRedirects(parsedRef.commandLine, projectReferenceRedirects);
addProjectReferenceRedirects(parsedRef.commandLine);
}
}
}
@@ -759,7 +776,9 @@ namespace ts {
isEmittedFile,
getConfigFileParsingDiagnostics,
getResolvedModuleWithFailedLookupLocationsFromCache,
getProjectReferences
getProjectReferences,
getResolvedProjectReferences,
getProjectReferenceRedirect
};
verifyCompilerOptions();
@@ -798,9 +817,9 @@ namespace ts {
// If a rootDir is specified use it as the commonSourceDirectory
commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, currentDirectory);
}
else if (options.composite) {
else if (options.composite && options.configFilePath) {
// Project compilations never infer their root from the input source paths
commonSourceDirectory = getDirectoryPath(normalizeSlashes(options.configFilePath!)); // TODO: GH#18217
commonSourceDirectory = getDirectoryPath(normalizeSlashes(options.configFilePath));
checkSourceFilesBelongToPath(emittedFiles, commonSourceDirectory);
}
else {
@@ -1007,15 +1026,22 @@ namespace ts {
}
// Check if any referenced project tsconfig files are different
const oldRefs = oldProgram.getProjectReferences();
// If array of references is changed, we cant resue old program
const oldProjectReferences = oldProgram.getProjectReferences();
if (!arrayIsEqualTo(oldProjectReferences!, projectReferences, projectReferenceIsEqualTo)) {
return oldProgram.structureIsReused = StructureIsReused.Not;
}
// Check the json files for the project references
const oldRefs = oldProgram.getResolvedProjectReferences();
if (projectReferences) {
if (!oldRefs) {
return oldProgram.structureIsReused = StructureIsReused.Not;
}
// Resolved project referenced should be array if projectReferences provided are array
Debug.assert(!!oldRefs);
for (let i = 0; i < projectReferences.length; i++) {
const oldRef = oldRefs[i];
const oldRef = oldRefs![i];
const newRef = parseProjectReferenceConfigFile(projectReferences[i]);
if (oldRef) {
const newRef = parseProjectReferenceConfigFile(projectReferences[i]);
if (!newRef || newRef.sourceFile !== oldRef.sourceFile) {
// Resolved project reference has gone missing or changed
return oldProgram.structureIsReused = StructureIsReused.Not;
@@ -1023,16 +1049,15 @@ namespace ts {
}
else {
// A previously-unresolved reference may be resolved now
if (parseProjectReferenceConfigFile(projectReferences[i]) !== undefined) {
if (newRef !== undefined) {
return oldProgram.structureIsReused = StructureIsReused.Not;
}
}
}
}
else {
if (oldRefs) {
return oldProgram.structureIsReused = StructureIsReused.Not;
}
// Resolved project referenced should be undefined if projectReferences is undefined
Debug.assert(!oldRefs);
}
// check if program source files has changed in the way that can affect structure of the program
@@ -1054,7 +1079,7 @@ namespace ts {
for (const oldSourceFile of oldSourceFiles) {
let newSourceFile = host.getSourceFileByPath
? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.resolvedPath || oldSourceFile.path, options.target!, /*onError*/ undefined, shouldCreateNewSourceFile)
? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.resolvedPath, options.target!, /*onError*/ undefined, shouldCreateNewSourceFile)
: host.getSourceFile(oldSourceFile.fileName, options.target!, /*onError*/ undefined, shouldCreateNewSourceFile); // TODO: GH#18217
if (!newSourceFile) {
@@ -1085,7 +1110,11 @@ namespace ts {
fileChanged = newSourceFile !== oldSourceFile;
}
// Since the project references havent changed, its right to set originalFileName and resolvedPath here
newSourceFile.path = oldSourceFile.path;
newSourceFile.originalFileName = oldSourceFile.originalFileName;
newSourceFile.resolvedPath = oldSourceFile.resolvedPath;
newSourceFile.fileName = oldSourceFile.fileName;
filePaths.push(newSourceFile.path);
const packageName = oldProgram.sourceFileToPackageName.get(oldSourceFile.path);
@@ -1162,7 +1191,7 @@ namespace ts {
modifiedFilePaths = modifiedSourceFiles.map(f => f.newFile.path);
// try to verify results of module resolution
for (const { oldFile: oldSourceFile, newFile: newSourceFile } of modifiedSourceFiles) {
const newSourceFilePath = getNormalizedAbsolutePath(newSourceFile.fileName, currentDirectory);
const newSourceFilePath = getNormalizedAbsolutePath(newSourceFile.originalFileName, currentDirectory);
if (resolveModuleNamesWorker) {
const moduleNames = getModuleNames(newSourceFile);
const oldProgramState: OldProgramState = { program: oldProgram, oldSourceFile, modifiedFilePaths };
@@ -1219,7 +1248,14 @@ namespace ts {
fileProcessingDiagnostics.reattachFileDiagnostics(modifiedFile.newFile);
}
resolvedTypeReferenceDirectives = oldProgram.getResolvedTypeReferenceDirectives();
resolvedProjectReferences = oldProgram.getProjectReferences();
resolvedProjectReferences = oldProgram.getResolvedProjectReferences();
if (resolvedProjectReferences) {
resolvedProjectReferences.forEach(ref => {
if (ref) {
addProjectReferenceRedirects(ref.commandLine);
}
});
}
sourceFileToPackageName = oldProgram.sourceFileToPackageName;
redirectTargetsMap = oldProgram.redirectTargetsMap;
@@ -1257,10 +1293,14 @@ namespace ts {
};
}
function getProjectReferences() {
function getResolvedProjectReferences() {
return resolvedProjectReferences;
}
function getProjectReferences() {
return projectReferences;
}
function getPrependNodes(): InputFiles[] {
if (!projectReferences) {
return emptyArray;
@@ -1271,12 +1311,13 @@ namespace ts {
const ref = projectReferences[i];
const resolvedRefOpts = resolvedProjectReferences![i]!.commandLine;
if (ref.prepend && resolvedRefOpts && resolvedRefOpts.options) {
const out = resolvedRefOpts.options.outFile || resolvedRefOpts.options.out;
// Upstream project didn't have outFile set -- skip (error will have been issued earlier)
if (!resolvedRefOpts.options.outFile) continue;
if (!out) continue;
const dtsFilename = changeExtension(resolvedRefOpts.options.outFile, ".d.ts");
const js = host.readFile(resolvedRefOpts.options.outFile) || `/* Input file ${resolvedRefOpts.options.outFile} was missing */\r\n`;
const jsMapPath = resolvedRefOpts.options.outFile + ".map"; // TODO: try to read sourceMappingUrl comment from the file
const dtsFilename = changeExtension(out, ".d.ts");
const js = host.readFile(out) || `/* Input file ${out} was missing */\r\n`;
const jsMapPath = out + ".map"; // TODO: try to read sourceMappingUrl comment from the file
const jsMap = host.readFile(jsMapPath);
const dts = host.readFile(dtsFilename) || `/* Input file ${dtsFilename} was missing */\r\n`;
const dtsMapPath = dtsFilename + ".map";
@@ -1351,7 +1392,7 @@ namespace ts {
...program.getSemanticDiagnostics(sourceFile, cancellationToken)
];
if (diagnostics.length === 0 && program.getCompilerOptions().declaration) {
if (diagnostics.length === 0 && getEmitDeclarations(program.getCompilerOptions())) {
declarationDiagnostics = program.getDeclarationDiagnostics(/*sourceFile*/ undefined, cancellationToken);
}
@@ -1438,9 +1479,9 @@ namespace ts {
function getSyntacticDiagnosticsForFile(sourceFile: SourceFile): ReadonlyArray<DiagnosticWithLocation> {
// For JavaScript files, we report semantic errors for using TypeScript-only
// constructs from within a JavaScript file as syntactic errors.
if (isSourceFileJavaScript(sourceFile)) {
if (isSourceFileJS(sourceFile)) {
if (!sourceFile.additionalSyntacticDiagnostics) {
sourceFile.additionalSyntacticDiagnostics = getJavaScriptSyntacticDiagnosticsForFile(sourceFile);
sourceFile.additionalSyntacticDiagnostics = getJSSyntacticDiagnosticsForFile(sourceFile);
}
return concatenate(sourceFile.additionalSyntacticDiagnostics, sourceFile.parseDiagnostics);
}
@@ -1538,7 +1579,7 @@ namespace ts {
return true;
}
function getJavaScriptSyntacticDiagnosticsForFile(sourceFile: SourceFile): DiagnosticWithLocation[] {
function getJSSyntacticDiagnosticsForFile(sourceFile: SourceFile): DiagnosticWithLocation[] {
return runWithCancellationToken(() => {
const diagnostics: DiagnosticWithLocation[] = [];
let parent: Node = sourceFile;
@@ -1801,7 +1842,7 @@ namespace ts {
return;
}
const isJavaScriptFile = isSourceFileJavaScript(file);
const isJavaScriptFile = isSourceFileJS(file);
const isExternalModuleFile = isExternalModule(file);
// file.imports may not be undefined if there exists dynamic import
@@ -1924,7 +1965,7 @@ namespace ts {
refFile?: SourceFile): SourceFile | undefined {
if (hasExtension(fileName)) {
if (!options.allowNonTsExtensions && !forEach(supportedExtensionsWithJsonIfResolveJsonModule || supportedExtensions, extension => fileExtensionIs(host.getCanonicalFileName(fileName), extension))) {
if (!options.allowNonTsExtensions && !forEach(supportedExtensionsWithJsonIfResolveJsonModule, extension => fileExtensionIs(host.getCanonicalFileName(fileName), extension))) {
if (fail) fail(Diagnostics.File_0_has_unsupported_extension_The_only_supported_extensions_are_1, fileName, "'" + supportedExtensions.join("', '") + "'");
return undefined;
}
@@ -1983,11 +2024,14 @@ namespace ts {
}
}
function createRedirectSourceFile(redirectTarget: SourceFile, unredirected: SourceFile, fileName: string, path: Path): SourceFile {
function createRedirectSourceFile(redirectTarget: SourceFile, unredirected: SourceFile, fileName: string, path: Path, resolvedPath: Path, originalFileName: string): SourceFile {
const redirect: SourceFile = Object.create(redirectTarget);
redirect.fileName = fileName;
redirect.path = path;
redirect.resolvedPath = resolvedPath;
redirect.originalFileName = originalFileName;
redirect.redirectInfo = { redirectTarget, unredirected };
sourceFilesFoundSearchingNodeModules.set(path, currentNodeModulesDepth > 0);
Object.defineProperties(redirect, {
id: {
get(this: SourceFile) { return this.redirectInfo!.redirectTarget.id; },
@@ -2003,6 +2047,7 @@ namespace ts {
// Get source file from normalized fileName
function findSourceFile(fileName: string, path: Path, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, refFile: SourceFile, refPos: number, refEnd: number, packageId: PackageId | undefined): SourceFile | undefined {
const originalFileName = fileName;
if (filesByName.has(path)) {
const file = filesByName.get(path);
// try to check if we've already seen this file but with a different casing in path
@@ -2076,7 +2121,7 @@ namespace ts {
if (fileFromPackageId) {
// Some other SourceFile already exists with this package name and version.
// Instead of creating a duplicate, just redirect to the existing one.
const dupFile = createRedirectSourceFile(fileFromPackageId, file!, fileName, path); // TODO: GH#18217
const dupFile = createRedirectSourceFile(fileFromPackageId, file!, fileName, path, toPath(fileName), originalFileName); // TODO: GH#18217
redirectTargetsMap.add(fileFromPackageId.path, fileName);
filesByName.set(path, dupFile);
sourceFileToPackageName.set(path, packageId.name);
@@ -2099,6 +2144,7 @@ namespace ts {
sourceFilesFoundSearchingNodeModules.set(path, currentNodeModulesDepth > 0);
file.path = path;
file.resolvedPath = toPath(fileName);
file.originalFileName = originalFileName;
if (host.useCaseSensitiveFileNames()) {
const pathLowerCase = path.toLowerCase();
@@ -2136,25 +2182,29 @@ namespace ts {
}
function getProjectReferenceRedirect(fileName: string): string | undefined {
const path = toPath(fileName);
// Ignore dts or any of the non ts files
if (!projectReferenceRedirects || fileExtensionIs(fileName, Extension.Dts) || !fileExtensionIsOneOf(fileName, supportedTSExtensions)) {
return undefined;
}
// If this file is produced by a referenced project, we need to rewrite it to
// look in the output folder of the referenced project rather than the input
const normalized = getNormalizedAbsolutePath(fileName, path);
let result: string | undefined;
projectReferenceRedirects.forEach((v, k) => {
if (result !== undefined) {
return forEach(projectReferenceRedirects, referencedProject => {
// not input file from the referenced project, ignore
if (!contains(referencedProject.fileNames, fileName, isSameFile)) {
return undefined;
}
if (normalized.indexOf(k) === 0) {
result = changeExtension(fileName.replace(k, v), ".d.ts");
}
const out = referencedProject.options.outFile || referencedProject.options.out;
return out ?
changeExtension(out, Extension.Dts) :
getOutputDeclarationFileName(fileName, referencedProject);
});
return result;
}
function processReferencedFiles(file: SourceFile, isDefaultLib: boolean) {
forEach(file.referencedFiles, ref => {
const referencedFileName = resolveTripleslashReference(ref.fileName, file.fileName);
const referencedFileName = resolveTripleslashReference(ref.fileName, file.originalFileName);
processSourceFile(referencedFileName, isDefaultLib, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined, file, ref.pos, ref.end);
});
}
@@ -2166,7 +2216,7 @@ namespace ts {
return;
}
const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeDirectives, file.fileName);
const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeDirectives, file.originalFileName);
for (let i = 0; i < typeDirectives.length; i++) {
const ref = file.typeReferenceDirectives[i];
@@ -2262,7 +2312,7 @@ namespace ts {
// Because global augmentation doesn't have string literal name, we can check for global augmentation as such.
const moduleNames = getModuleNames(file);
const oldProgramState: OldProgramState = { program: oldProgram, oldSourceFile: oldProgram && oldProgram.getSourceFile(file.fileName), modifiedFilePaths };
const resolutions = resolveModuleNamesReusingOldState(moduleNames, getNormalizedAbsolutePath(file.fileName, currentDirectory), file, oldProgramState);
const resolutions = resolveModuleNamesReusingOldState(moduleNames, getNormalizedAbsolutePath(file.originalFileName, currentDirectory), file, oldProgramState);
Debug.assert(resolutions.length === moduleNames.length);
for (let i = 0; i < moduleNames.length; i++) {
const resolution = resolutions[i];
@@ -2273,7 +2323,7 @@ namespace ts {
}
const isFromNodeModulesSearch = resolution.isExternalLibraryImport;
const isJsFile = !resolutionExtensionIsTypeScriptOrJson(resolution.extension);
const isJsFile = !resolutionExtensionIsTSOrJson(resolution.extension);
const isJsFileFromNodeModules = isFromNodeModulesSearch && isJsFile;
const resolvedFileName = resolution.resolvedFileName;
@@ -2295,7 +2345,7 @@ namespace ts {
&& i < file.imports.length
&& !elideImport
&& !(isJsFile && !options.allowJs)
&& (isInJavaScriptFile(file.imports[i]) || !(file.imports[i].flags & NodeFlags.JSDoc));
&& (isInJSFile(file.imports[i]) || !(file.imports[i].flags & NodeFlags.JSDoc));
if (elideImport) {
modulesWithElidedImports.set(file.path, true);
@@ -2341,7 +2391,7 @@ namespace ts {
function parseProjectReferenceConfigFile(ref: ProjectReference): { commandLine: ParsedCommandLine, sourceFile: SourceFile } | undefined {
// The actual filename (i.e. add "/tsconfig.json" if necessary)
const refPath = resolveProjectReferencePath(host, ref);
const refPath = resolveProjectReferencePath(ref);
// An absolute path pointing to the containing directory of the config file
const basePath = getNormalizedAbsolutePath(getDirectoryPath(refPath), host.getCurrentDirectory());
const sourceFile = host.getSourceFile(refPath, ScriptTarget.JSON) as JsonSourceFile | undefined;
@@ -2353,15 +2403,8 @@ namespace ts {
return { commandLine, sourceFile };
}
function addProjectReferenceRedirects(referencedProject: ParsedCommandLine, target: Map<string>) {
const rootDir = normalizePath(referencedProject.options.rootDir || getDirectoryPath(referencedProject.options.configFilePath!)); // TODO: GH#18217
target.set(rootDir, getDeclarationOutputDirectory(referencedProject));
}
function getDeclarationOutputDirectory(proj: ParsedCommandLine) {
return proj.options.declarationDir ||
proj.options.outDir ||
getDirectoryPath(proj.options.configFilePath!); // TODO: GH#18217
function addProjectReferenceRedirects(referencedProject: ParsedCommandLine) {
(projectReferenceRedirects || (projectReferenceRedirects = [])).push(referencedProject);
}
function verifyCompilerOptions() {
@@ -2370,8 +2413,8 @@ namespace ts {
}
if (options.isolatedModules) {
if (options.declaration) {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "declaration", "isolatedModules");
if (getEmitDeclarations(options)) {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, getEmitDeclarationOptionName(options), "isolatedModules");
}
if (options.noEmitOnError) {
@@ -2418,9 +2461,10 @@ namespace ts {
createDiagnosticForReference(i, Diagnostics.Referenced_project_0_must_have_setting_composite_Colon_true, ref.path);
}
if (ref.prepend) {
if (resolvedRefOpts.outFile) {
if (!host.fileExists(resolvedRefOpts.outFile)) {
createDiagnosticForReference(i, Diagnostics.Output_file_0_from_project_1_does_not_exist, resolvedRefOpts.outFile, ref.path);
const out = resolvedRefOpts.outFile || resolvedRefOpts.out;
if (out) {
if (!host.fileExists(out)) {
createDiagnosticForReference(i, Diagnostics.Output_file_0_from_project_1_does_not_exist, out, ref.path);
}
}
else {
@@ -2494,8 +2538,8 @@ namespace ts {
}
if (options.declarationDir) {
if (!options.declaration) {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "declarationDir", "declaration");
if (!getEmitDeclarations(options)) {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "declarationDir", "declaration", "composite");
}
if (options.out || options.outFile) {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "declarationDir", options.out ? "out" : "outFile");
@@ -2503,7 +2547,7 @@ namespace ts {
}
if (options.declarationMap && !getEmitDeclarations(options)) {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "declarationMap", "declaration");
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "declarationMap", "declaration", "composite");
}
if (options.lib && options.noLib) {
@@ -2536,7 +2580,7 @@ namespace ts {
}
// Cannot specify module gen that isn't amd or system with --out
if (outFile) {
if (outFile && !options.emitDeclarationOnly) {
if (options.module && !(options.module === ModuleKind.AMD || options.module === ModuleKind.System)) {
createDiagnosticForOptionName(Diagnostics.Only_amd_and_system_modules_are_supported_alongside_0, options.out ? "out" : "outFile", "module");
}
@@ -2571,8 +2615,8 @@ namespace ts {
}
}
if (!options.noEmit && options.allowJs && options.declaration) {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "allowJs", "declaration");
if (!options.noEmit && options.allowJs && getEmitDeclarations(options)) {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "allowJs", getEmitDeclarationOptionName(options));
}
if (options.checkJs && !options.allowJs) {
@@ -2580,8 +2624,8 @@ namespace ts {
}
if (options.emitDeclarationOnly) {
if (!options.declaration) {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "emitDeclarationOnly", "declaration");
if (!getEmitDeclarations(options)) {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "emitDeclarationOnly", "declaration", "composite");
}
if (options.noEmit) {
@@ -2794,7 +2838,7 @@ namespace ts {
return containsPath(options.outDir, filePath, currentDirectory, !host.useCaseSensitiveFileNames());
}
if (fileExtensionIsOneOf(filePath, supportedJavascriptExtensions) || fileExtensionIs(filePath, Extension.Dts)) {
if (fileExtensionIsOneOf(filePath, supportedJSExtensions) || fileExtensionIs(filePath, Extension.Dts)) {
// Otherwise just check if sourceFile with the name exists
const filePathWithoutExtension = removeFileExtension(filePath);
return !!getSourceFileByPath((filePathWithoutExtension + Extension.Ts) as Path) ||
@@ -2812,7 +2856,10 @@ namespace ts {
export function parseConfigHostFromCompilerHost(host: CompilerHost): ParseConfigFileHost {
return {
fileExists: f => host.fileExists(f),
readDirectory: (root, extensions, includes, depth?) => host.readDirectory ? host.readDirectory(root, extensions, includes, depth) : [],
readDirectory(root, extensions, excludes, includes, depth) {
Debug.assertDefined(host.readDirectory, "'CompilerHost.readDirectory' must be implemented to correctly process 'projectReferences'");
return host.readDirectory!(root, extensions, excludes, includes, depth);
},
readFile: f => host.readFile(f),
useCaseSensitiveFileNames: host.useCaseSensitiveFileNames(),
getCurrentDirectory: () => host.getCurrentDirectory(),
@@ -2820,20 +2867,25 @@ namespace ts {
};
}
export interface ResolveProjectReferencePathHost {
// For backward compatibility
/** @deprecated */ export interface ResolveProjectReferencePathHost {
fileExists(fileName: string): boolean;
}
/**
* Returns the target config filename of a project reference.
* Note: The file might not exist.
*/
export function resolveProjectReferencePath(host: ResolveProjectReferencePathHost, ref: ProjectReference): ResolvedConfigFileName {
if (!host.fileExists(ref.path)) {
return combinePaths(ref.path, "tsconfig.json") as ResolvedConfigFileName;
}
return ref.path as ResolvedConfigFileName;
export function resolveProjectReferencePath(ref: ProjectReference): ResolvedConfigFileName;
/** @deprecated */ export function resolveProjectReferencePath(host: ResolveProjectReferencePathHost, ref: ProjectReference): ResolvedConfigFileName;
export function resolveProjectReferencePath(hostOrRef: ResolveProjectReferencePathHost | ProjectReference, ref?: ProjectReference): ResolvedConfigFileName {
const passedInRef = ref ? ref : hostOrRef as ProjectReference;
return resolveConfigFileProjectName(passedInRef.path);
}
function getEmitDeclarationOptionName(options: CompilerOptions) {
return options.declaration ? "declaration" : "composite";
}
/* @internal */
/**
* Returns a DiagnosticMessage if we won't include a resolved module due to its extension.
+3 -2
View File
@@ -226,7 +226,7 @@ namespace ts {
// otherwise try to load typings from @types
const globalCache = resolutionHost.getGlobalCache();
if (globalCache !== undefined && !isExternalModuleNameRelative(moduleName) && !(primaryResult.resolvedModule && extensionIsTypeScript(primaryResult.resolvedModule.extension))) {
if (globalCache !== undefined && !isExternalModuleNameRelative(moduleName) && !(primaryResult.resolvedModule && extensionIsTS(primaryResult.resolvedModule.extension))) {
// create different collection of failed lookup locations for second pass
// if it will fail and we've already found something during the first pass - we don't want to pollute its results
const { resolvedModule, failedLookupLocations } = loadModuleFromGlobalCache(moduleName, resolutionHost.projectName, compilerOptions, host, globalCache);
@@ -398,7 +398,8 @@ namespace ts {
function getDirectoryToWatchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path): DirectoryOfFailedLookupWatch {
if (isInDirectoryPath(rootPath, failedLookupLocationPath)) {
failedLookupLocation = isRootedDiskPath(failedLookupLocation) ? failedLookupLocation : getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory());
// Ensure failed look up is normalized path
failedLookupLocation = isRootedDiskPath(failedLookupLocation) ? normalizePath(failedLookupLocation) : getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory());
Debug.assert(failedLookupLocation.length === failedLookupLocationPath.length, `FailedLookup: ${failedLookupLocation} failedLookupLocationPath: ${failedLookupLocationPath}`); // tslint:disable-line
const subDirectoryInRoot = failedLookupLocationPath.indexOf(directorySeparator, rootPath.length + 1);
if (subDirectoryInRoot !== -1) {
+86 -80
View File
@@ -60,81 +60,87 @@ namespace ts {
tryScan<T>(callback: () => T): T;
}
const textToToken = createMapFromTemplate({
"abstract": SyntaxKind.AbstractKeyword,
"any": SyntaxKind.AnyKeyword,
"as": SyntaxKind.AsKeyword,
"boolean": SyntaxKind.BooleanKeyword,
"break": SyntaxKind.BreakKeyword,
"case": SyntaxKind.CaseKeyword,
"catch": SyntaxKind.CatchKeyword,
"class": SyntaxKind.ClassKeyword,
"continue": SyntaxKind.ContinueKeyword,
"const": SyntaxKind.ConstKeyword,
"constructor": SyntaxKind.ConstructorKeyword,
"debugger": SyntaxKind.DebuggerKeyword,
"declare": SyntaxKind.DeclareKeyword,
"default": SyntaxKind.DefaultKeyword,
"delete": SyntaxKind.DeleteKeyword,
"do": SyntaxKind.DoKeyword,
"else": SyntaxKind.ElseKeyword,
"enum": SyntaxKind.EnumKeyword,
"export": SyntaxKind.ExportKeyword,
"extends": SyntaxKind.ExtendsKeyword,
"false": SyntaxKind.FalseKeyword,
"finally": SyntaxKind.FinallyKeyword,
"for": SyntaxKind.ForKeyword,
"from": SyntaxKind.FromKeyword,
"function": SyntaxKind.FunctionKeyword,
"get": SyntaxKind.GetKeyword,
"if": SyntaxKind.IfKeyword,
"implements": SyntaxKind.ImplementsKeyword,
"import": SyntaxKind.ImportKeyword,
"in": SyntaxKind.InKeyword,
"infer": SyntaxKind.InferKeyword,
"instanceof": SyntaxKind.InstanceOfKeyword,
"interface": SyntaxKind.InterfaceKeyword,
"is": SyntaxKind.IsKeyword,
"keyof": SyntaxKind.KeyOfKeyword,
"let": SyntaxKind.LetKeyword,
"module": SyntaxKind.ModuleKeyword,
"namespace": SyntaxKind.NamespaceKeyword,
"never": SyntaxKind.NeverKeyword,
"new": SyntaxKind.NewKeyword,
"null": SyntaxKind.NullKeyword,
"number": SyntaxKind.NumberKeyword,
"object": SyntaxKind.ObjectKeyword,
"package": SyntaxKind.PackageKeyword,
"private": SyntaxKind.PrivateKeyword,
"protected": SyntaxKind.ProtectedKeyword,
"public": SyntaxKind.PublicKeyword,
"readonly": SyntaxKind.ReadonlyKeyword,
"require": SyntaxKind.RequireKeyword,
"global": SyntaxKind.GlobalKeyword,
"return": SyntaxKind.ReturnKeyword,
"set": SyntaxKind.SetKeyword,
"static": SyntaxKind.StaticKeyword,
"string": SyntaxKind.StringKeyword,
"super": SyntaxKind.SuperKeyword,
"switch": SyntaxKind.SwitchKeyword,
"symbol": SyntaxKind.SymbolKeyword,
"this": SyntaxKind.ThisKeyword,
"throw": SyntaxKind.ThrowKeyword,
"true": SyntaxKind.TrueKeyword,
"try": SyntaxKind.TryKeyword,
"type": SyntaxKind.TypeKeyword,
"typeof": SyntaxKind.TypeOfKeyword,
"undefined": SyntaxKind.UndefinedKeyword,
"unique": SyntaxKind.UniqueKeyword,
"unknown": SyntaxKind.UnknownKeyword,
"var": SyntaxKind.VarKeyword,
"void": SyntaxKind.VoidKeyword,
"while": SyntaxKind.WhileKeyword,
"with": SyntaxKind.WithKeyword,
"yield": SyntaxKind.YieldKeyword,
"async": SyntaxKind.AsyncKeyword,
"await": SyntaxKind.AwaitKeyword,
"of": SyntaxKind.OfKeyword,
const textToKeywordObj: MapLike<KeywordSyntaxKind> = {
abstract: SyntaxKind.AbstractKeyword,
any: SyntaxKind.AnyKeyword,
as: SyntaxKind.AsKeyword,
boolean: SyntaxKind.BooleanKeyword,
break: SyntaxKind.BreakKeyword,
case: SyntaxKind.CaseKeyword,
catch: SyntaxKind.CatchKeyword,
class: SyntaxKind.ClassKeyword,
continue: SyntaxKind.ContinueKeyword,
const: SyntaxKind.ConstKeyword,
["" + "constructor"]: SyntaxKind.ConstructorKeyword,
debugger: SyntaxKind.DebuggerKeyword,
declare: SyntaxKind.DeclareKeyword,
default: SyntaxKind.DefaultKeyword,
delete: SyntaxKind.DeleteKeyword,
do: SyntaxKind.DoKeyword,
else: SyntaxKind.ElseKeyword,
enum: SyntaxKind.EnumKeyword,
export: SyntaxKind.ExportKeyword,
extends: SyntaxKind.ExtendsKeyword,
false: SyntaxKind.FalseKeyword,
finally: SyntaxKind.FinallyKeyword,
for: SyntaxKind.ForKeyword,
from: SyntaxKind.FromKeyword,
function: SyntaxKind.FunctionKeyword,
get: SyntaxKind.GetKeyword,
if: SyntaxKind.IfKeyword,
implements: SyntaxKind.ImplementsKeyword,
import: SyntaxKind.ImportKeyword,
in: SyntaxKind.InKeyword,
infer: SyntaxKind.InferKeyword,
instanceof: SyntaxKind.InstanceOfKeyword,
interface: SyntaxKind.InterfaceKeyword,
is: SyntaxKind.IsKeyword,
keyof: SyntaxKind.KeyOfKeyword,
let: SyntaxKind.LetKeyword,
module: SyntaxKind.ModuleKeyword,
namespace: SyntaxKind.NamespaceKeyword,
never: SyntaxKind.NeverKeyword,
new: SyntaxKind.NewKeyword,
null: SyntaxKind.NullKeyword,
number: SyntaxKind.NumberKeyword,
object: SyntaxKind.ObjectKeyword,
package: SyntaxKind.PackageKeyword,
private: SyntaxKind.PrivateKeyword,
protected: SyntaxKind.ProtectedKeyword,
public: SyntaxKind.PublicKeyword,
readonly: SyntaxKind.ReadonlyKeyword,
require: SyntaxKind.RequireKeyword,
global: SyntaxKind.GlobalKeyword,
return: SyntaxKind.ReturnKeyword,
set: SyntaxKind.SetKeyword,
static: SyntaxKind.StaticKeyword,
string: SyntaxKind.StringKeyword,
super: SyntaxKind.SuperKeyword,
switch: SyntaxKind.SwitchKeyword,
symbol: SyntaxKind.SymbolKeyword,
this: SyntaxKind.ThisKeyword,
throw: SyntaxKind.ThrowKeyword,
true: SyntaxKind.TrueKeyword,
try: SyntaxKind.TryKeyword,
type: SyntaxKind.TypeKeyword,
typeof: SyntaxKind.TypeOfKeyword,
undefined: SyntaxKind.UndefinedKeyword,
unique: SyntaxKind.UniqueKeyword,
unknown: SyntaxKind.UnknownKeyword,
var: SyntaxKind.VarKeyword,
void: SyntaxKind.VoidKeyword,
while: SyntaxKind.WhileKeyword,
with: SyntaxKind.WithKeyword,
yield: SyntaxKind.YieldKeyword,
async: SyntaxKind.AsyncKeyword,
await: SyntaxKind.AwaitKeyword,
of: SyntaxKind.OfKeyword,
};
const textToKeyword = createMapFromTemplate(textToKeywordObj);
const textToToken = createMapFromTemplate<SyntaxKind>({
...textToKeywordObj,
"{": SyntaxKind.OpenBraceToken,
"}": SyntaxKind.CloseBraceToken,
"(": SyntaxKind.OpenParenToken,
@@ -1288,15 +1294,15 @@ namespace ts {
return result;
}
function getIdentifierToken(): SyntaxKind {
function getIdentifierToken(): SyntaxKind.Identifier | KeywordSyntaxKind {
// Reserved words are between 2 and 11 characters long and start with a lowercase letter
const len = tokenValue.length;
if (len >= 2 && len <= 11) {
const ch = tokenValue.charCodeAt(0);
if (ch >= CharacterCodes.a && ch <= CharacterCodes.z) {
token = textToToken.get(tokenValue)!;
if (token !== undefined) {
return token;
const keyword = textToKeyword.get(tokenValue);
if (keyword !== undefined) {
return token = keyword;
}
}
}
@@ -2016,7 +2022,7 @@ namespace ts {
pos++;
}
tokenValue = text.substring(tokenPos, pos);
return token = SyntaxKind.Identifier;
return token = getIdentifierToken();
}
else {
return token = SyntaxKind.Unknown;
+391
View File
@@ -0,0 +1,391 @@
/* @internal */
namespace ts {
// https://semver.org/#spec-item-2
// > A normal version number MUST take the form X.Y.Z where X, Y, and Z are non-negative
// > integers, and MUST NOT contain leading zeroes. X is the major version, Y is the minor
// > version, and Z is the patch version. Each element MUST increase numerically.
//
// NOTE: We differ here in that we allow X and X.Y, with missing parts having the default
// value of `0`.
const versionRegExp = /^(0|[1-9]\d*)(?:\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*)(?:\-([a-z0-9-.]+))?(?:\+([a-z0-9-.]+))?)?)?$/i;
// https://semver.org/#spec-item-9
// > A pre-release version MAY be denoted by appending a hyphen and a series of dot separated
// > identifiers immediately following the patch version. Identifiers MUST comprise only ASCII
// > alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. Numeric identifiers
// > MUST NOT include leading zeroes.
const prereleaseRegExp = /^(?:0|[1-9]\d*|[a-z-][a-z0-9-]*)(?:\.(?:0|[1-9]\d*|[a-z-][a-z0-9-]*))*$/i;
// https://semver.org/#spec-item-10
// > Build metadata MAY be denoted by appending a plus sign and a series of dot separated
// > identifiers immediately following the patch or pre-release version. Identifiers MUST
// > comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty.
const buildRegExp = /^[a-z0-9-]+(?:\.[a-z0-9-]+)*$/i;
// https://semver.org/#spec-item-9
// > Numeric identifiers MUST NOT include leading zeroes.
const numericIdentifierRegExp = /^(0|[1-9]\d*)$/;
/**
* Describes a precise semantic version number, https://semver.org
*/
export class Version {
static readonly zero = new Version(0, 0, 0);
readonly major: number;
readonly minor: number;
readonly patch: number;
readonly prerelease: ReadonlyArray<string>;
readonly build: ReadonlyArray<string>;
constructor(text: string);
constructor(major: number, minor?: number, patch?: number, prerelease?: string, build?: string);
constructor(major: number | string, minor = 0, patch = 0, prerelease = "", build = "") {
if (typeof major === "string") {
const result = Debug.assertDefined(tryParseComponents(major), "Invalid version");
({ major, minor, patch, prerelease, build } = result);
}
Debug.assert(major >= 0, "Invalid argument: major");
Debug.assert(minor >= 0, "Invalid argument: minor");
Debug.assert(patch >= 0, "Invalid argument: patch");
Debug.assert(!prerelease || prereleaseRegExp.test(prerelease), "Invalid argument: prerelease");
Debug.assert(!build || buildRegExp.test(build), "Invalid argument: build");
this.major = major;
this.minor = minor;
this.patch = patch;
this.prerelease = prerelease ? prerelease.split(".") : emptyArray;
this.build = build ? build.split(".") : emptyArray;
}
static tryParse(text: string) {
const result = tryParseComponents(text);
if (!result) return undefined;
const { major, minor, patch, prerelease, build } = result;
return new Version(major, minor, patch, prerelease, build);
}
compareTo(other: Version | undefined) {
// https://semver.org/#spec-item-11
// > Precedence is determined by the first difference when comparing each of these
// > identifiers from left to right as follows: Major, minor, and patch versions are
// > always compared numerically.
//
// https://semver.org/#spec-item-11
// > Precedence for two pre-release versions with the same major, minor, and patch version
// > MUST be determined by comparing each dot separated identifier from left to right until
// > a difference is found [...]
//
// https://semver.org/#spec-item-11
// > Build metadata does not figure into precedence
if (this === other) return Comparison.EqualTo;
if (other === undefined) return Comparison.GreaterThan;
return compareValues(this.major, other.major)
|| compareValues(this.minor, other.minor)
|| compareValues(this.patch, other.patch)
|| comparePrerelaseIdentifiers(this.prerelease, other.prerelease);
}
increment(field: "major" | "minor" | "patch") {
switch (field) {
case "major": return new Version(this.major + 1, 0, 0);
case "minor": return new Version(this.major, this.minor + 1, 0);
case "patch": return new Version(this.major, this.minor, this.patch + 1);
default: return Debug.assertNever(field);
}
}
toString() {
let result = `${this.major}.${this.minor}.${this.patch}`;
if (some(this.prerelease)) result += `-${this.prerelease.join(".")}`;
if (some(this.build)) result += `+${this.build.join(".")}`;
return result;
}
}
function tryParseComponents(text: string) {
const match = versionRegExp.exec(text);
if (!match) return undefined;
const [, major, minor = "0", patch = "0", prerelease = "", build = ""] = match;
if (prerelease && !prereleaseRegExp.test(prerelease)) return undefined;
if (build && !buildRegExp.test(build)) return undefined;
return {
major: parseInt(major, 10),
minor: parseInt(minor, 10),
patch: parseInt(patch, 10),
prerelease,
build
};
}
function comparePrerelaseIdentifiers(left: ReadonlyArray<string>, right: ReadonlyArray<string>) {
// https://semver.org/#spec-item-11
// > When major, minor, and patch are equal, a pre-release version has lower precedence
// > than a normal version.
if (left === right) return Comparison.EqualTo;
if (left.length === 0) return right.length === 0 ? Comparison.EqualTo : Comparison.GreaterThan;
if (right.length === 0) return Comparison.LessThan;
// https://semver.org/#spec-item-11
// > Precedence for two pre-release versions with the same major, minor, and patch version
// > MUST be determined by comparing each dot separated identifier from left to right until
// > a difference is found [...]
const length = Math.min(left.length, right.length);
for (let i = 0; i < length; i++) {
const leftIdentifier = left[i];
const rightIdentifier = right[i];
if (leftIdentifier === rightIdentifier) continue;
const leftIsNumeric = numericIdentifierRegExp.test(leftIdentifier);
const rightIsNumeric = numericIdentifierRegExp.test(rightIdentifier);
if (leftIsNumeric || rightIsNumeric) {
// https://semver.org/#spec-item-11
// > Numeric identifiers always have lower precedence than non-numeric identifiers.
if (leftIsNumeric !== rightIsNumeric) return leftIsNumeric ? Comparison.LessThan : Comparison.GreaterThan;
// https://semver.org/#spec-item-11
// > identifiers consisting of only digits are compared numerically
const result = compareValues(+leftIdentifier, +rightIdentifier);
if (result) return result;
}
else {
// https://semver.org/#spec-item-11
// > identifiers with letters or hyphens are compared lexically in ASCII sort order.
const result = compareStringsCaseSensitive(leftIdentifier, rightIdentifier);
if (result) return result;
}
}
// https://semver.org/#spec-item-11
// > A larger set of pre-release fields has a higher precedence than a smaller set, if all
// > of the preceding identifiers are equal.
return compareValues(left.length, right.length);
}
/**
* Describes a semantic version range, per https://github.com/npm/node-semver#ranges
*/
export class VersionRange {
private _alternatives: ReadonlyArray<ReadonlyArray<Comparator>>;
constructor(spec: string) {
this._alternatives = spec ? Debug.assertDefined(parseRange(spec), "Invalid range spec.") : emptyArray;
}
static tryParse(text: string) {
const sets = parseRange(text);
if (sets) {
const range = new VersionRange("");
range._alternatives = sets;
return range;
}
return undefined;
}
test(version: Version | string) {
if (typeof version === "string") version = new Version(version);
return testDisjunction(version, this._alternatives);
}
toString() {
return formatDisjunction(this._alternatives);
}
}
interface Comparator {
readonly operator: "<" | "<=" | ">" | ">=" | "=";
readonly operand: Version;
}
// https://github.com/npm/node-semver#range-grammar
//
// range-set ::= range ( logical-or range ) *
// range ::= hyphen | simple ( ' ' simple ) * | ''
// logical-or ::= ( ' ' ) * '||' ( ' ' ) *
const logicalOrRegExp = /\s*\|\|\s*/g;
const whitespaceRegExp = /\s+/g;
// https://github.com/npm/node-semver#range-grammar
//
// partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )?
// xr ::= 'x' | 'X' | '*' | nr
// nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) *
// qualifier ::= ( '-' pre )? ( '+' build )?
// pre ::= parts
// build ::= parts
// parts ::= part ( '.' part ) *
// part ::= nr | [-0-9A-Za-z]+
const partialRegExp = /^([xX*0]|[1-9]\d*)(?:\.([xX*0]|[1-9]\d*)(?:\.([xX*0]|[1-9]\d*)(?:-([a-z0-9-.]+))?(?:\+([a-z0-9-.]+))?)?)?$/i;
// https://github.com/npm/node-semver#range-grammar
//
// hyphen ::= partial ' - ' partial
const hyphenRegExp = /^\s*([a-z0-9-+.*]+)\s+-\s+([a-z0-9-+.*]+)\s*$/i;
// https://github.com/npm/node-semver#range-grammar
//
// simple ::= primitive | partial | tilde | caret
// primitive ::= ( '<' | '>' | '>=' | '<=' | '=' ) partial
// tilde ::= '~' partial
// caret ::= '^' partial
const rangeRegExp = /^\s*(~|\^|<|<=|>|>=|=)?\s*([a-z0-9-+.*]+)$/i;
function parseRange(text: string) {
const alternatives: Comparator[][] = [];
for (const range of text.trim().split(logicalOrRegExp)) {
if (!range) continue;
const comparators: Comparator[] = [];
const match = hyphenRegExp.exec(range);
if (match) {
if (!parseHyphen(match[1], match[2], comparators)) return undefined;
}
else {
for (const simple of range.split(whitespaceRegExp)) {
const match = rangeRegExp.exec(simple);
if (!match || !parseComparator(match[1], match[2], comparators)) return undefined;
}
}
alternatives.push(comparators);
}
return alternatives;
}
function parsePartial(text: string) {
const match = partialRegExp.exec(text);
if (!match) return undefined;
const [, major, minor = "*", patch = "*", prerelease, build] = match;
const version = new Version(
isWildcard(major) ? 0 : parseInt(major, 10),
isWildcard(major) || isWildcard(minor) ? 0 : parseInt(minor, 10),
isWildcard(major) || isWildcard(minor) || isWildcard(patch) ? 0 : parseInt(patch, 10),
prerelease,
build);
return { version, major, minor, patch };
}
function parseHyphen(left: string, right: string, comparators: Comparator[]) {
const leftResult = parsePartial(left);
if (!leftResult) return false;
const rightResult = parsePartial(right);
if (!rightResult) return false;
if (!isWildcard(leftResult.major)) {
comparators.push(createComparator(">=", leftResult.version));
}
if (!isWildcard(rightResult.major)) {
comparators.push(
isWildcard(rightResult.minor) ? createComparator("<", rightResult.version.increment("major")) :
isWildcard(rightResult.patch) ? createComparator("<", rightResult.version.increment("minor")) :
createComparator("<=", rightResult.version));
}
return true;
}
function parseComparator(operator: string, text: string, comparators: Comparator[]) {
const result = parsePartial(text);
if (!result) return false;
const { version, major, minor, patch } = result;
if (!isWildcard(major)) {
switch (operator) {
case "~":
comparators.push(createComparator(">=", version));
comparators.push(createComparator("<", version.increment(
isWildcard(minor) ? "major" :
"minor")));
break;
case "^":
comparators.push(createComparator(">=", version));
comparators.push(createComparator("<", version.increment(
version.major > 0 || isWildcard(minor) ? "major" :
version.minor > 0 || isWildcard(patch) ? "minor" :
"patch")));
break;
case "<":
case ">=":
comparators.push(createComparator(operator, version));
break;
case "<=":
case ">":
comparators.push(
isWildcard(minor) ? createComparator(operator === "<=" ? "<" : ">=", version.increment("major")) :
isWildcard(patch) ? createComparator(operator === "<=" ? "<" : ">=", version.increment("minor")) :
createComparator(operator, version));
break;
case "=":
case undefined:
if (isWildcard(minor) || isWildcard(patch)) {
comparators.push(createComparator(">=", version));
comparators.push(createComparator("<", version.increment(isWildcard(minor) ? "major" : "minor")));
}
else {
comparators.push(createComparator("=", version));
}
break;
default:
// unrecognized
return false;
}
}
else if (operator === "<" || operator === ">") {
comparators.push(createComparator("<", Version.zero));
}
return true;
}
function isWildcard(part: string) {
return part === "*" || part === "x" || part === "X";
}
function createComparator(operator: Comparator["operator"], operand: Version) {
return { operator, operand };
}
function testDisjunction(version: Version, alternatives: ReadonlyArray<ReadonlyArray<Comparator>>) {
// an empty disjunction is treated as "*" (all versions)
if (alternatives.length === 0) return true;
for (const alternative of alternatives) {
if (testAlternative(version, alternative)) return true;
}
return false;
}
function testAlternative(version: Version, comparators: ReadonlyArray<Comparator>) {
for (const comparator of comparators) {
if (!testComparator(version, comparator.operator, comparator.operand)) return false;
}
return true;
}
function testComparator(version: Version, operator: Comparator["operator"], operand: Version) {
const cmp = version.compareTo(operand);
switch (operator) {
case "<": return cmp < 0;
case "<=": return cmp <= 0;
case ">": return cmp > 0;
case ">=": return cmp >= 0;
case "=": return cmp === 0;
default: return Debug.assertNever(operator);
}
}
function formatDisjunction(alternatives: ReadonlyArray<ReadonlyArray<Comparator>>) {
return map(alternatives, formatAlternative).join(" || ") || "*";
}
function formatAlternative(comparators: ReadonlyArray<Comparator>) {
return map(comparators, formatComparator).join(" ");
}
function formatComparator(comparator: Comparator) {
return `${comparator.operator}${comparator.operand}`;
}
}
+14 -28
View File
@@ -36,23 +36,6 @@ namespace ts {
Low = 250
}
function getPriorityValues(highPriorityValue: number): [number, number, number] {
const mediumPriorityValue = highPriorityValue * 2;
const lowPriorityValue = mediumPriorityValue * 4;
return [highPriorityValue, mediumPriorityValue, lowPriorityValue];
}
function pollingInterval(watchPriority: PollingInterval): number {
return pollingIntervalsForPriority[watchPriority];
}
const pollingIntervalsForPriority = getPriorityValues(250);
/* @internal */
export function watchFileUsingPriorityPollingInterval(host: System, fileName: string, callback: FileWatcherCallback, watchPriority: PollingInterval): FileWatcher {
return host.watchFile!(fileName, callback, pollingInterval(watchPriority));
}
/* @internal */
export type HostWatchFile = (fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval | undefined) => FileWatcher;
/* @internal */
@@ -317,18 +300,22 @@ namespace ts {
const newTime = modifiedTime.getTime();
if (oldTime !== newTime) {
watchedFile.mtime = modifiedTime;
const eventKind = oldTime === 0
? FileWatcherEventKind.Created
: newTime === 0
? FileWatcherEventKind.Deleted
: FileWatcherEventKind.Changed;
watchedFile.callback(watchedFile.fileName, eventKind);
watchedFile.callback(watchedFile.fileName, getFileWatcherEventKind(oldTime, newTime));
return true;
}
return false;
}
/*@internal*/
export function getFileWatcherEventKind(oldTime: number, newTime: number) {
return oldTime === 0
? FileWatcherEventKind.Created
: newTime === 0
? FileWatcherEventKind.Deleted
: FileWatcherEventKind.Changed;
}
/*@internal*/
export interface RecursiveDirectoryWatcherHost {
watchDirectory: HostWatchDirectory;
@@ -792,11 +779,10 @@ namespace ts {
dirName,
(_eventName: string, relativeFileName) => {
// When files are deleted from disk, the triggered "rename" event would have a relativefileName of "undefined"
const fileName = !isString(relativeFileName)
? undefined! // TODO: GH#18217
: getNormalizedAbsolutePath(relativeFileName, dirName);
if (!isString(relativeFileName)) { return; }
const fileName = getNormalizedAbsolutePath(relativeFileName, dirName);
// Some applications save a working file via rename operations
const callbacks = fileWatcherCallbacks.get(toCanonicalName(fileName));
const callbacks = fileName && fileWatcherCallbacks.get(toCanonicalName(fileName));
if (callbacks) {
for (const fileCallback of callbacks) {
fileCallback(fileName, FileWatcherEventKind.Changed);
@@ -843,7 +829,7 @@ namespace ts {
}
}
type FsWatchCallback = (eventName: "rename" | "change", relativeFileName: string) => void;
type FsWatchCallback = (eventName: "rename" | "change", relativeFileName: string | undefined) => void;
function createFileWatcherCallback(callback: FsWatchCallback): FileWatcherCallback {
return (_fileName, eventKind) => callback(eventKind === FileWatcherEventKind.Changed ? "change" : "rename", "");
+14 -7
View File
@@ -1,11 +1,11 @@
/*@internal*/
namespace ts {
export function getDeclarationDiagnostics(host: EmitHost, resolver: EmitResolver, file: SourceFile | undefined): DiagnosticWithLocation[] | undefined {
if (file && isSourceFileJavaScript(file)) {
if (file && isSourceFileJS(file)) {
return []; // No declaration diagnostics for js for now
}
const compilerOptions = host.getCompilerOptions();
const result = transformNodes(resolver, host, compilerOptions, file ? [file] : filter(host.getSourceFiles(), isSourceFileNotJavaScript), [transformDeclarations], /*allowDtsFiles*/ false);
const result = transformNodes(resolver, host, compilerOptions, file ? [file] : filter(host.getSourceFiles(), isSourceFileNotJS), [transformDeclarations], /*allowDtsFiles*/ false);
return result.diagnostics;
}
@@ -157,7 +157,7 @@ namespace ts {
function transformRoot(node: SourceFile): SourceFile;
function transformRoot(node: SourceFile | Bundle): SourceFile | Bundle;
function transformRoot(node: SourceFile | Bundle) {
if (node.kind === SyntaxKind.SourceFile && (node.isDeclarationFile || isSourceFileJavaScript(node))) {
if (node.kind === SyntaxKind.SourceFile && (node.isDeclarationFile || isSourceFileJS(node))) {
return node;
}
@@ -168,7 +168,7 @@ namespace ts {
let hasNoDefaultLib = false;
const bundle = createBundle(map(node.sourceFiles,
sourceFile => {
if (sourceFile.isDeclarationFile || isSourceFileJavaScript(sourceFile)) return undefined!; // Omit declaration files from bundle results, too // TODO: GH#18217
if (sourceFile.isDeclarationFile || isSourceFileJS(sourceFile)) return undefined!; // Omit declaration files from bundle results, too // TODO: GH#18217
hasNoDefaultLib = hasNoDefaultLib || sourceFile.hasNoDefaultLib;
currentSourceFile = sourceFile;
enclosingDeclaration = sourceFile;
@@ -275,7 +275,7 @@ namespace ts {
else {
if (isBundledEmit && contains((node as Bundle).sourceFiles, file)) return; // Omit references to files which are being merged
const paths = getOutputPathsFor(file, host, /*forceDtsPaths*/ true);
declFileName = paths.declarationFilePath || paths.jsFilePath;
declFileName = paths.declarationFilePath || paths.jsFilePath || file.fileName;
}
if (declFileName) {
@@ -289,6 +289,13 @@ namespace ts {
if (startsWith(fileName, "./") && hasExtension(fileName)) {
fileName = fileName.substring(2);
}
// omit references to files from node_modules (npm may disambiguate module
// references when installing this package, making the path is unreliable).
if (startsWith(fileName, "node_modules/") || fileName.indexOf("/node_modules/") !== -1) {
return;
}
references.push({ pos: -1, end: -1, fileName });
}
};
@@ -296,7 +303,7 @@ namespace ts {
}
function collectReferences(sourceFile: SourceFile, ret: Map<SourceFile>) {
if (noResolve || isSourceFileJavaScript(sourceFile)) return ret;
if (noResolve || isSourceFileJS(sourceFile)) return ret;
forEach(sourceFile.referencedFiles, f => {
const elem = tryResolveScriptReference(host, sourceFile, f);
if (elem) {
@@ -982,7 +989,7 @@ namespace ts {
ensureType(input, input.type),
/*body*/ undefined
));
if (clean && resolver.isJSContainerFunctionDeclaration(input)) {
if (clean && resolver.isExpandoFunctionDeclaration(input)) {
const declarations = mapDefined(resolver.getPropertiesOfContainerFunction(input), p => {
if (!isPropertyAccessExpression(p.valueDeclaration)) {
return undefined;
@@ -434,7 +434,7 @@ namespace ts {
// Heritage clause is written by user so it can always be named
if (node.parent.parent.kind === SyntaxKind.ClassDeclaration) {
// Class or Interface implemented/extended is inaccessible
diagnosticMessage = (node as ExpressionWithTypeArguments).parent.token === SyntaxKind.ImplementsKeyword ?
diagnosticMessage = isHeritageClause(node.parent) && node.parent.token === SyntaxKind.ImplementsKeyword ?
Diagnostics.Implements_clause_of_exported_class_0_has_or_is_using_private_name_1 :
Diagnostics.extends_clause_of_exported_class_0_has_or_is_using_private_name_1;
}
@@ -446,7 +446,7 @@ namespace ts {
return {
diagnosticMessage,
errorNode: node,
typeName: getNameOfDeclaration((node as ExpressionWithTypeArguments).parent.parent)
typeName: getNameOfDeclaration(node.parent.parent as Declaration)
};
}
@@ -466,4 +466,4 @@ namespace ts {
};
}
}
}
}
+3 -3
View File
@@ -307,8 +307,8 @@ namespace ts {
if (!getRestIndicatorOfBindingOrAssignmentElement(element)) {
const propertyName = getPropertyNameOfBindingOrAssignmentElement(element)!;
if (flattenContext.level >= FlattenLevel.ObjectRest
&& !(element.transformFlags & (TransformFlags.ContainsRest | TransformFlags.ContainsObjectRest))
&& !(getTargetOfBindingOrAssignmentElement(element)!.transformFlags & (TransformFlags.ContainsRest | TransformFlags.ContainsObjectRest))
&& !(element.transformFlags & (TransformFlags.ContainsRestOrSpread | TransformFlags.ContainsObjectRestOrSpread))
&& !(getTargetOfBindingOrAssignmentElement(element)!.transformFlags & (TransformFlags.ContainsRestOrSpread | TransformFlags.ContainsObjectRestOrSpread))
&& !isComputedPropertyName(propertyName)) {
bindingElements = append(bindingElements, element);
}
@@ -384,7 +384,7 @@ namespace ts {
if (flattenContext.level >= FlattenLevel.ObjectRest) {
// If an array pattern contains an ObjectRest, we must cache the result so that we
// can perform the ObjectRest destructuring in a different declaration
if (element.transformFlags & TransformFlags.ContainsObjectRest) {
if (element.transformFlags & TransformFlags.ContainsObjectRestOrSpread) {
const temp = createTempVariable(/*recordTempVariable*/ undefined);
if (flattenContext.hoistTempVariables) {
flattenContext.context.hoistVariableDeclaration(temp);
+458 -161
View File
@@ -46,10 +46,16 @@ namespace ts {
* so nobody can observe this new value.
*/
interface LoopOutParameter {
flags: LoopOutParameterFlags;
originalName: Identifier;
outParamName: Identifier;
}
const enum LoopOutParameterFlags {
Body = 1 << 0, // Modified in the body of the iteration statement
Initializer = 1 << 1, // Set in the initializer of a ForStatement
}
const enum CopyDirection {
ToOriginal,
ToOutParameter
@@ -129,10 +135,14 @@ namespace ts {
*/
hoistedLocalVariables?: Identifier[];
conditionVariable?: Identifier;
loopParameters: ParameterDeclaration[];
/**
* List of loop out parameters - detailed descripion can be found in the comment to LoopOutParameter
*/
loopOutParameters?: LoopOutParameter[];
loopOutParameters: LoopOutParameter[];
}
const enum SuperCaptureResult {
@@ -347,7 +357,7 @@ namespace ts {
return (node.transformFlags & TransformFlags.ContainsES2015) !== 0
|| convertedLoopState !== undefined
|| (hierarchyFacts & HierarchyFacts.ConstructorWithCapturedSuper && (isStatement(node) || (node.kind === SyntaxKind.Block)))
|| (isIterationStatement(node, /*lookInLabeledStatements*/ false) && shouldConvertIterationStatementBody(node))
|| (isIterationStatement(node, /*lookInLabeledStatements*/ false) && shouldConvertIterationStatement(node))
|| (getEmitFlags(node) & EmitFlags.TypeScriptClassWrapper) !== 0;
}
@@ -646,8 +656,8 @@ namespace ts {
}
}
let returnExpression: Expression = createLiteral(labelMarker);
if (convertedLoopState.loopOutParameters!.length) {
const outParams = convertedLoopState.loopOutParameters!;
if (convertedLoopState.loopOutParameters.length) {
const outParams = convertedLoopState.loopOutParameters;
let expr: Expression | undefined;
for (let i = 0; i < outParams.length; i++) {
const copyExpr = copyOutParameter(outParams[i], CopyDirection.ToOutParameter);
@@ -2610,7 +2620,40 @@ namespace ts {
return visitEachChild(node, visitor, context);
}
function shouldConvertIterationStatementBody(node: IterationStatement): boolean {
interface ForStatementWithConvertibleInitializer extends ForStatement {
initializer: VariableDeclarationList;
}
interface ForStatementWithConvertibleCondition extends ForStatement {
condition: Expression;
}
interface ForStatementWithConvertibleIncrementor extends ForStatement {
incrementor: Expression;
}
function shouldConvertPartOfIterationStatement(node: Node) {
return (resolver.getNodeCheckFlags(node) & NodeCheckFlags.ContainsCapturedBlockScopeBinding) !== 0;
}
function shouldConvertInitializerOfForStatement(node: IterationStatement): node is ForStatementWithConvertibleInitializer {
return isForStatement(node) && !!node.initializer && shouldConvertPartOfIterationStatement(node.initializer);
}
function shouldConvertConditionOfForStatement(node: IterationStatement): node is ForStatementWithConvertibleCondition {
return isForStatement(node) && !!node.condition && shouldConvertPartOfIterationStatement(node.condition);
}
function shouldConvertIncrementorOfForStatement(node: IterationStatement): node is ForStatementWithConvertibleIncrementor {
return isForStatement(node) && !!node.incrementor && shouldConvertPartOfIterationStatement(node.incrementor);
}
function shouldConvertIterationStatement(node: IterationStatement) {
return shouldConvertBodyOfIterationStatement(node)
|| shouldConvertInitializerOfForStatement(node);
}
function shouldConvertBodyOfIterationStatement(node: IterationStatement): boolean {
return (resolver.getNodeCheckFlags(node) & NodeCheckFlags.LoopWithCapturedBlockScopedBinding) !== 0;
}
@@ -2639,7 +2682,7 @@ namespace ts {
}
function convertIterationStatementBodyIfNecessary(node: IterationStatement, outermostLabeledStatement: LabeledStatement | undefined, convert?: LoopConverter): VisitResult<Statement> {
if (!shouldConvertIterationStatementBody(node)) {
if (!shouldConvertIterationStatement(node)) {
let saveAllowedNonLabeledJumps: Jump | undefined;
if (convertedLoopState) {
// we get here if we are trying to emit normal loop loop inside converted loop
@@ -2658,7 +2701,102 @@ namespace ts {
return result;
}
const functionName = createUniqueName("_loop");
const currentState = createConvertedLoopState(node);
const statements: Statement[] = [];
const outerConvertedLoopState = convertedLoopState;
convertedLoopState = currentState;
const initializerFunction = shouldConvertInitializerOfForStatement(node) ? createFunctionForInitializerOfForStatement(node, currentState) : undefined;
const bodyFunction = shouldConvertBodyOfIterationStatement(node) ? createFunctionForBodyOfIterationStatement(node, currentState, outerConvertedLoopState) : undefined;
convertedLoopState = outerConvertedLoopState;
if (initializerFunction) statements.push(initializerFunction.functionDeclaration);
if (bodyFunction) statements.push(bodyFunction.functionDeclaration);
addExtraDeclarationsForConvertedLoop(statements, currentState, outerConvertedLoopState);
if (initializerFunction) {
statements.push(generateCallToConvertedLoopInitializer(initializerFunction.functionName, initializerFunction.containsYield));
}
let loop: Statement;
if (bodyFunction) {
if (convert) {
loop = convert(node, outermostLabeledStatement, bodyFunction.part);
}
else {
const clone = convertIterationStatementCore(node, initializerFunction, createBlock(bodyFunction.part, /*multiLine*/ true));
aggregateTransformFlags(clone);
loop = restoreEnclosingLabel(clone, outermostLabeledStatement, convertedLoopState && resetLabel);
}
}
else {
const clone = convertIterationStatementCore(node, initializerFunction, visitNode(node.statement, visitor, isStatement, liftToBlock));
aggregateTransformFlags(clone);
loop = restoreEnclosingLabel(clone, outermostLabeledStatement, convertedLoopState && resetLabel);
}
statements.push(loop);
return statements;
}
function convertIterationStatementCore(node: IterationStatement, initializerFunction: IterationStatementPartFunction<VariableDeclarationList> | undefined, convertedLoopBody: Statement) {
switch (node.kind) {
case SyntaxKind.ForStatement: return convertForStatement(node as ForStatement, initializerFunction, convertedLoopBody);
case SyntaxKind.ForInStatement: return convertForInStatement(node as ForInStatement, convertedLoopBody);
case SyntaxKind.ForOfStatement: return convertForOfStatement(node as ForOfStatement, convertedLoopBody);
case SyntaxKind.DoStatement: return convertDoStatement(node as DoStatement, convertedLoopBody);
case SyntaxKind.WhileStatement: return convertWhileStatement(node as WhileStatement, convertedLoopBody);
default: return Debug.failBadSyntaxKind(node, "IterationStatement expected");
}
}
function convertForStatement(node: ForStatement, initializerFunction: IterationStatementPartFunction<VariableDeclarationList> | undefined, convertedLoopBody: Statement) {
const shouldConvertCondition = node.condition && shouldConvertPartOfIterationStatement(node.condition);
const shouldConvertIncrementor = shouldConvertCondition || node.incrementor && shouldConvertPartOfIterationStatement(node.incrementor);
return updateFor(
node,
visitNode(initializerFunction ? initializerFunction.part : node.initializer, visitor, isForInitializer),
visitNode(shouldConvertCondition ? undefined : node.condition, visitor, isExpression),
visitNode(shouldConvertIncrementor ? undefined : node.incrementor, visitor, isExpression),
convertedLoopBody
);
}
function convertForOfStatement(node: ForOfStatement, convertedLoopBody: Statement) {
return updateForOf(
node,
/*awaitModifier*/ undefined,
visitNode(node.initializer, visitor, isForInitializer),
visitNode(node.expression, visitor, isExpression),
convertedLoopBody);
}
function convertForInStatement(node: ForInStatement, convertedLoopBody: Statement) {
return updateForIn(
node,
visitNode(node.initializer, visitor, isForInitializer),
visitNode(node.expression, visitor, isExpression),
convertedLoopBody);
}
function convertDoStatement(node: DoStatement, convertedLoopBody: Statement) {
return updateDo(
node,
convertedLoopBody,
visitNode(node.expression, visitor, isExpression));
}
function convertWhileStatement(node: WhileStatement, convertedLoopBody: Statement) {
return updateWhile(
node,
visitNode(node.expression, visitor, isExpression),
convertedLoopBody);
}
function createConvertedLoopState(node: IterationStatement) {
let loopInitializer: VariableDeclarationList | undefined;
switch (node.kind) {
case SyntaxKind.ForStatement:
@@ -2670,74 +2808,311 @@ namespace ts {
}
break;
}
// variables that will be passed to the loop as parameters
const loopParameters: ParameterDeclaration[] = [];
// variables declared in the loop initializer that will be changed inside the loop
const loopOutParameters: LoopOutParameter[] = [];
if (loopInitializer && (getCombinedNodeFlags(loopInitializer) & NodeFlags.BlockScoped)) {
const hasCapturedBindingsInForInitializer = shouldConvertInitializerOfForStatement(node);
for (const decl of loopInitializer.declarations) {
processLoopVariableDeclaration(decl, loopParameters, loopOutParameters);
processLoopVariableDeclaration(node, decl, loopParameters, loopOutParameters, hasCapturedBindingsInForInitializer);
}
}
const outerConvertedLoopState = convertedLoopState;
convertedLoopState = { loopOutParameters };
if (outerConvertedLoopState) {
const currentState: ConvertedLoopState = { loopParameters, loopOutParameters };
if (convertedLoopState) {
// convertedOuterLoopState !== undefined means that this converted loop is nested in another converted loop.
// if outer converted loop has already accumulated some state - pass it through
if (outerConvertedLoopState.argumentsName) {
if (convertedLoopState.argumentsName) {
// outer loop has already used 'arguments' so we've already have some name to alias it
// use the same name in all nested loops
convertedLoopState.argumentsName = outerConvertedLoopState.argumentsName;
currentState.argumentsName = convertedLoopState.argumentsName;
}
if (outerConvertedLoopState.thisName) {
if (convertedLoopState.thisName) {
// outer loop has already used 'this' so we've already have some name to alias it
// use the same name in all nested loops
convertedLoopState.thisName = outerConvertedLoopState.thisName;
currentState.thisName = convertedLoopState.thisName;
}
if (outerConvertedLoopState.hoistedLocalVariables) {
if (convertedLoopState.hoistedLocalVariables) {
// we've already collected some non-block scoped variable declarations in enclosing loop
// use the same storage in nested loop
convertedLoopState.hoistedLocalVariables = outerConvertedLoopState.hoistedLocalVariables;
currentState.hoistedLocalVariables = convertedLoopState.hoistedLocalVariables;
}
}
return currentState;
}
function addExtraDeclarationsForConvertedLoop(statements: Statement[], state: ConvertedLoopState, outerState: ConvertedLoopState | undefined) {
let extraVariableDeclarations: VariableDeclaration[] | undefined;
// propagate state from the inner loop to the outer loop if necessary
if (state.argumentsName) {
// if alias for arguments is set
if (outerState) {
// pass it to outer converted loop
outerState.argumentsName = state.argumentsName;
}
else {
// this is top level converted loop and we need to create an alias for 'arguments' object
(extraVariableDeclarations || (extraVariableDeclarations = [])).push(
createVariableDeclaration(
state.argumentsName,
/*type*/ undefined,
createIdentifier("arguments")
)
);
}
}
if (state.thisName) {
// if alias for this is set
if (outerState) {
// pass it to outer converted loop
outerState.thisName = state.thisName;
}
else {
// this is top level converted loop so we need to create an alias for 'this' here
// NOTE:
// if converted loops were all nested in arrow function then we'll always emit '_this' so convertedLoopState.thisName will not be set.
// If it is set this means that all nested loops are not nested in arrow function and it is safe to capture 'this'.
(extraVariableDeclarations || (extraVariableDeclarations = [])).push(
createVariableDeclaration(
state.thisName,
/*type*/ undefined,
createIdentifier("this")
)
);
}
}
if (state.hoistedLocalVariables) {
// if hoistedLocalVariables !== undefined this means that we've possibly collected some variable declarations to be hoisted later
if (outerState) {
// pass them to outer converted loop
outerState.hoistedLocalVariables = state.hoistedLocalVariables;
}
else {
if (!extraVariableDeclarations) {
extraVariableDeclarations = [];
}
// hoist collected variable declarations
for (const identifier of state.hoistedLocalVariables) {
extraVariableDeclarations.push(createVariableDeclaration(identifier));
}
}
}
// add extra variables to hold out parameters if necessary
if (state.loopOutParameters.length) {
if (!extraVariableDeclarations) {
extraVariableDeclarations = [];
}
for (const outParam of state.loopOutParameters) {
extraVariableDeclarations.push(createVariableDeclaration(outParam.outParamName));
}
}
if (state.conditionVariable) {
if (!extraVariableDeclarations) {
extraVariableDeclarations = [];
}
extraVariableDeclarations.push(createVariableDeclaration(state.conditionVariable, /*type*/ undefined, createFalse()));
}
// create variable statement to hold all introduced variable declarations
if (extraVariableDeclarations) {
statements.push(createVariableStatement(
/*modifiers*/ undefined,
createVariableDeclarationList(extraVariableDeclarations)
));
}
}
interface IterationStatementPartFunction<T> {
functionName: Identifier;
functionDeclaration: Statement;
containsYield: boolean;
part: T;
}
function createOutVariable(p: LoopOutParameter) {
return createVariableDeclaration(p.originalName, /*type*/ undefined, p.outParamName);
}
/**
* Creates a `_loop_init` function for a `ForStatement` with a block-scoped initializer
* that is captured in a closure inside of the initializer. The `_loop_init` function is
* used to preserve the per-iteration environment semantics of
* [13.7.4.8 RS: ForBodyEvaluation](https://tc39.github.io/ecma262/#sec-forbodyevaluation).
*/
function createFunctionForInitializerOfForStatement(node: ForStatementWithConvertibleInitializer, currentState: ConvertedLoopState): IterationStatementPartFunction<VariableDeclarationList> {
const functionName = createUniqueName("_loop_init");
const containsYield = (node.initializer.transformFlags & TransformFlags.ContainsYield) !== 0;
let emitFlags = EmitFlags.None;
if (currentState.containsLexicalThis) emitFlags |= EmitFlags.CapturesThis;
if (containsYield && hierarchyFacts & HierarchyFacts.AsyncFunctionBody) emitFlags |= EmitFlags.AsyncFunctionBody;
const statements: Statement[] = [];
statements.push(createVariableStatement(/*modifiers*/ undefined, node.initializer));
copyOutParameters(currentState.loopOutParameters, LoopOutParameterFlags.Initializer, CopyDirection.ToOutParameter, statements);
// This transforms the following ES2015 syntax:
//
// for (let i = (setImmediate(() => console.log(i)), 0); i < 2; i++) {
// // loop body
// }
//
// Into the following ES5 syntax:
//
// var _loop_init_1 = function () {
// var i = (setImmediate(() => console.log(i)), 0);
// out_i_1 = i;
// };
// var out_i_1;
// _loop_init_1();
// for (var i = out_i_1; i < 2; i++) {
// // loop body
// }
//
// Which prevents mutations to `i` in the per-iteration environment of the body
// from affecting the initial value for `i` outside of the per-iteration environment.
const functionDeclaration = createVariableStatement(
/*modifiers*/ undefined,
setEmitFlags(
createVariableDeclarationList([
createVariableDeclaration(
functionName,
/*type*/ undefined,
setEmitFlags(
createFunctionExpression(
/*modifiers*/ undefined,
containsYield ? createToken(SyntaxKind.AsteriskToken) : undefined,
/*name*/ undefined,
/*typeParameters*/ undefined,
/*parameters*/ undefined,
/*type*/ undefined,
visitNode(
createBlock(statements, /*multiLine*/ true),
visitor,
isBlock
)
),
emitFlags
)
)
]),
EmitFlags.NoHoisting
)
);
const part = createVariableDeclarationList(map(currentState.loopOutParameters, createOutVariable));
return { functionName, containsYield, functionDeclaration, part };
}
/**
* Creates a `_loop` function for an `IterationStatement` with a block-scoped initializer
* that is captured in a closure inside of the loop body. The `_loop` function is used to
* preserve the per-iteration environment semantics of
* [13.7.4.8 RS: ForBodyEvaluation](https://tc39.github.io/ecma262/#sec-forbodyevaluation).
*/
function createFunctionForBodyOfIterationStatement(node: IterationStatement, currentState: ConvertedLoopState, outerState: ConvertedLoopState | undefined): IterationStatementPartFunction<Statement[]> {
const functionName = createUniqueName("_loop");
startLexicalEnvironment();
let loopBody = visitNode(node.statement, visitor, isStatement, liftToBlock);
const statement = visitNode(node.statement, visitor, isStatement, liftToBlock);
const lexicalEnvironment = endLexicalEnvironment();
const currentState = convertedLoopState;
convertedLoopState = outerConvertedLoopState;
const statements: Statement[] = [];
if (shouldConvertConditionOfForStatement(node) || shouldConvertIncrementorOfForStatement(node)) {
// If a block-scoped variable declared in the initializer of `node` is captured in
// the condition or incrementor, we must move the condition and incrementor into
// the body of the for loop.
//
// This transforms the following ES2015 syntax:
//
// for (let i = 0; setImmediate(() => console.log(i)), i < 2; setImmediate(() => console.log(i)), i++) {
// // loop body
// }
//
// Into the following ES5 syntax:
//
// var _loop_1 = function (i) {
// if (inc_1)
// setImmediate(() => console.log(i)), i++;
// else
// inc_1 = true;
// if (!(setImmediate(() => console.log(i)), i < 2))
// return out_i_1 = i, "break";
// // loop body
// out_i_1 = i;
// }
// var out_i_1, inc_1 = false;
// for (var i = 0;;) {
// var state_1 = _loop_1(i);
// i = out_i_1;
// if (state_1 === "break")
// break;
// }
//
// Which prevents mutations to `i` in the per-iteration environment of the body
// from affecting the value of `i` in the previous per-iteration environment.
//
// Note that the incrementor of a `for` loop is evaluated in a *new* per-iteration
// environment that is carried over to the next iteration of the loop. As a result,
// we must indicate whether this is the first evaluation of the loop body so that
// we only evaluate the incrementor on subsequent evaluations.
if (loopOutParameters.length || lexicalEnvironment) {
const statements = isBlock(loopBody) ? loopBody.statements.slice() : [loopBody];
if (loopOutParameters.length) {
copyOutParameters(loopOutParameters, CopyDirection.ToOutParameter, statements);
currentState.conditionVariable = createUniqueName("inc");
statements.push(createIf(
currentState.conditionVariable,
createStatement(visitNode(node.incrementor, visitor, isExpression)),
createStatement(createAssignment(currentState.conditionVariable, createTrue()))
));
if (shouldConvertConditionOfForStatement(node)) {
statements.push(createIf(
createPrefix(SyntaxKind.ExclamationToken, visitNode(node.condition, visitor, isExpression)),
visitNode(createBreak(), visitor, isStatement)
));
}
addStatementsAfterPrologue(statements, lexicalEnvironment);
loopBody = createBlock(statements, /*multiline*/ true);
}
if (isBlock(loopBody)) {
loopBody.multiLine = true;
if (isBlock(statement)) {
addRange(statements, statement.statements);
}
else {
loopBody = createBlock([loopBody], /*multiline*/ true);
statements.push(statement);
}
copyOutParameters(currentState.loopOutParameters, LoopOutParameterFlags.Body, CopyDirection.ToOutParameter, statements);
addStatementsAfterPrologue(statements, lexicalEnvironment);
const loopBody = createBlock(statements, /*multiLine*/ true);
if (isBlock(statement)) setOriginalNode(loopBody, statement);
const containsYield = (node.statement.transformFlags & TransformFlags.ContainsYield) !== 0;
const isAsyncBlockContainingAwait = containsYield && (hierarchyFacts & HierarchyFacts.AsyncFunctionBody) !== 0;
let loopBodyFlags: EmitFlags = 0;
if (currentState.containsLexicalThis) {
loopBodyFlags |= EmitFlags.CapturesThis;
}
let emitFlags: EmitFlags = 0;
if (currentState.containsLexicalThis) emitFlags |= EmitFlags.CapturesThis;
if (containsYield && (hierarchyFacts & HierarchyFacts.AsyncFunctionBody) !== 0) emitFlags |= EmitFlags.AsyncFunctionBody;
if (isAsyncBlockContainingAwait) {
loopBodyFlags |= EmitFlags.AsyncFunctionBody;
}
// This transforms the following ES2015 syntax (in addition to other variations):
//
// for (let i = 0; i < 2; i++) {
// setImmediate(() => console.log(i));
// }
//
// Into the following ES5 syntax:
//
// var _loop_1 = function (i) {
// setImmediate(() => console.log(i));
// };
// for (var i = 0; i < 2; i++) {
// _loop_1(i);
// }
const convertedLoopVariable =
const functionDeclaration =
createVariableStatement(
/*modifiers*/ undefined,
setEmitFlags(
@@ -2752,11 +3127,11 @@ namespace ts {
containsYield ? createToken(SyntaxKind.AsteriskToken) : undefined,
/*name*/ undefined,
/*typeParameters*/ undefined,
loopParameters,
currentState.loopParameters,
/*type*/ undefined,
<Block>loopBody
loopBody
),
loopBodyFlags
emitFlags
)
)
]
@@ -2765,106 +3140,8 @@ namespace ts {
)
);
const statements: Statement[] = [convertedLoopVariable];
let extraVariableDeclarations: VariableDeclaration[] | undefined;
// propagate state from the inner loop to the outer loop if necessary
if (currentState.argumentsName) {
// if alias for arguments is set
if (outerConvertedLoopState) {
// pass it to outer converted loop
outerConvertedLoopState.argumentsName = currentState.argumentsName;
}
else {
// this is top level converted loop and we need to create an alias for 'arguments' object
(extraVariableDeclarations || (extraVariableDeclarations = [])).push(
createVariableDeclaration(
currentState.argumentsName,
/*type*/ undefined,
createIdentifier("arguments")
)
);
}
}
if (currentState.thisName) {
// if alias for this is set
if (outerConvertedLoopState) {
// pass it to outer converted loop
outerConvertedLoopState.thisName = currentState.thisName;
}
else {
// this is top level converted loop so we need to create an alias for 'this' here
// NOTE:
// if converted loops were all nested in arrow function then we'll always emit '_this' so convertedLoopState.thisName will not be set.
// If it is set this means that all nested loops are not nested in arrow function and it is safe to capture 'this'.
(extraVariableDeclarations || (extraVariableDeclarations = [])).push(
createVariableDeclaration(
currentState.thisName,
/*type*/ undefined,
createIdentifier("this")
)
);
}
}
if (currentState.hoistedLocalVariables) {
// if hoistedLocalVariables !== undefined this means that we've possibly collected some variable declarations to be hoisted later
if (outerConvertedLoopState) {
// pass them to outer converted loop
outerConvertedLoopState.hoistedLocalVariables = currentState.hoistedLocalVariables;
}
else {
if (!extraVariableDeclarations) {
extraVariableDeclarations = [];
}
// hoist collected variable declarations
for (const identifier of currentState.hoistedLocalVariables) {
extraVariableDeclarations.push(createVariableDeclaration(identifier));
}
}
}
// add extra variables to hold out parameters if necessary
if (loopOutParameters.length) {
if (!extraVariableDeclarations) {
extraVariableDeclarations = [];
}
for (const outParam of loopOutParameters) {
extraVariableDeclarations.push(createVariableDeclaration(outParam.outParamName));
}
}
// create variable statement to hold all introduced variable declarations
if (extraVariableDeclarations) {
statements.push(createVariableStatement(
/*modifiers*/ undefined,
createVariableDeclarationList(extraVariableDeclarations)
));
}
const convertedLoopBodyStatements = generateCallToConvertedLoop(functionName, loopParameters, currentState, containsYield);
let loop: Statement;
if (convert) {
loop = convert(node, outermostLabeledStatement, convertedLoopBodyStatements);
}
else {
let clone = getMutableClone(node);
// clean statement part
clone.statement = undefined!;
// visit childnodes to transform initializer/condition/incrementor parts
clone = visitEachChild(clone, visitor, context);
// set loop statement
clone.statement = createBlock(convertedLoopBodyStatements, /*multiline*/ true);
// reset and re-aggregate the transform flags
clone.transformFlags = 0;
aggregateTransformFlags(clone);
loop = restoreEnclosingLabel(clone, outermostLabeledStatement, convertedLoopState && resetLabel);
}
statements.push(loop);
return statements;
const part = generateCallToConvertedLoop(functionName, currentState, outerState, containsYield);
return { functionName, containsYield, functionDeclaration, part };
}
function copyOutParameter(outParam: LoopOutParameter, copyDirection: CopyDirection): BinaryExpression {
@@ -2873,14 +3150,26 @@ namespace ts {
return createBinary(target, SyntaxKind.EqualsToken, source);
}
function copyOutParameters(outParams: LoopOutParameter[], copyDirection: CopyDirection, statements: Statement[]): void {
function copyOutParameters(outParams: LoopOutParameter[], partFlags: LoopOutParameterFlags, copyDirection: CopyDirection, statements: Statement[]): void {
for (const outParam of outParams) {
statements.push(createExpressionStatement(copyOutParameter(outParam, copyDirection)));
if (outParam.flags & partFlags) {
statements.push(createExpressionStatement(copyOutParameter(outParam, copyDirection)));
}
}
}
function generateCallToConvertedLoop(loopFunctionExpressionName: Identifier, parameters: ParameterDeclaration[], state: ConvertedLoopState, isAsyncBlockContainingAwait: boolean): Statement[] {
const outerConvertedLoopState = convertedLoopState;
function generateCallToConvertedLoopInitializer(initFunctionExpressionName: Identifier, containsYield: boolean): Statement {
const call = createCall(initFunctionExpressionName, /*typeArguments*/ undefined, []);
const callResult = containsYield
? createYield(
createToken(SyntaxKind.AsteriskToken),
setEmitFlags(call, EmitFlags.Iterator)
)
: call;
return createStatement(callResult);
}
function generateCallToConvertedLoop(loopFunctionExpressionName: Identifier, state: ConvertedLoopState, outerState: ConvertedLoopState | undefined, containsYield: boolean): Statement[] {
const statements: Statement[] = [];
// loop is considered simple if it does not have any return statements or break\continue that transfer control outside of the loop
@@ -2891,8 +3180,8 @@ namespace ts {
!state.labeledNonLocalBreaks &&
!state.labeledNonLocalContinues;
const call = createCall(loopFunctionExpressionName, /*typeArguments*/ undefined, map(parameters, p => <Identifier>p.name));
const callResult = isAsyncBlockContainingAwait
const call = createCall(loopFunctionExpressionName, /*typeArguments*/ undefined, map(state.loopParameters, p => <Identifier>p.name));
const callResult = containsYield
? createYield(
createToken(SyntaxKind.AsteriskToken),
setEmitFlags(call, EmitFlags.Iterator)
@@ -2900,7 +3189,7 @@ namespace ts {
: call;
if (isSimpleLoop) {
statements.push(createExpressionStatement(callResult));
copyOutParameters(state.loopOutParameters!, CopyDirection.ToOriginal, statements);
copyOutParameters(state.loopOutParameters, LoopOutParameterFlags.Body, CopyDirection.ToOriginal, statements);
}
else {
const loopResultName = createUniqueName("state");
@@ -2911,12 +3200,12 @@ namespace ts {
)
);
statements.push(stateVariable);
copyOutParameters(state.loopOutParameters!, CopyDirection.ToOriginal, statements);
copyOutParameters(state.loopOutParameters, LoopOutParameterFlags.Body, CopyDirection.ToOriginal, statements);
if (state.nonLocalJumps! & Jump.Return) {
let returnStatement: ReturnStatement;
if (outerConvertedLoopState) {
outerConvertedLoopState.nonLocalJumps! |= Jump.Return;
if (outerState) {
outerState.nonLocalJumps! |= Jump.Return;
returnStatement = createReturn(loopResultName);
}
else {
@@ -2949,8 +3238,8 @@ namespace ts {
if (state.labeledNonLocalBreaks || state.labeledNonLocalContinues) {
const caseClauses: CaseClause[] = [];
processLabeledJumps(state.labeledNonLocalBreaks!, /*isBreak*/ true, loopResultName, outerConvertedLoopState, caseClauses);
processLabeledJumps(state.labeledNonLocalContinues!, /*isBreak*/ false, loopResultName, outerConvertedLoopState, caseClauses);
processLabeledJumps(state.labeledNonLocalBreaks!, /*isBreak*/ true, loopResultName, outerState, caseClauses);
processLabeledJumps(state.labeledNonLocalContinues!, /*isBreak*/ false, loopResultName, outerState, caseClauses);
statements.push(
createSwitch(
loopResultName,
@@ -2998,20 +3287,28 @@ namespace ts {
});
}
function processLoopVariableDeclaration(decl: VariableDeclaration | BindingElement, loopParameters: ParameterDeclaration[], loopOutParameters: LoopOutParameter[]) {
function processLoopVariableDeclaration(container: IterationStatement, decl: VariableDeclaration | BindingElement, loopParameters: ParameterDeclaration[], loopOutParameters: LoopOutParameter[], hasCapturedBindingsInForInitializer: boolean) {
const name = decl.name;
if (isBindingPattern(name)) {
for (const element of name.elements) {
if (!isOmittedExpression(element)) {
processLoopVariableDeclaration(element, loopParameters, loopOutParameters);
processLoopVariableDeclaration(container, element, loopParameters, loopOutParameters, hasCapturedBindingsInForInitializer);
}
}
}
else {
loopParameters.push(createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, name));
if (resolver.getNodeCheckFlags(decl) & NodeCheckFlags.NeedsLoopOutParameter) {
const checkFlags = resolver.getNodeCheckFlags(decl);
if (checkFlags & NodeCheckFlags.NeedsLoopOutParameter || hasCapturedBindingsInForInitializer) {
const outParamName = createUniqueName("out_" + idText(name));
loopOutParameters.push({ originalName: name, outParamName });
let flags: LoopOutParameterFlags = 0;
if (checkFlags & NodeCheckFlags.NeedsLoopOutParameter) {
flags |= LoopOutParameterFlags.Body;
}
if (isForStatement(container) && container.initializer && resolver.isBindingCapturedByNode(container.initializer, decl)) {
flags |= LoopOutParameterFlags.Initializer;
}
loopOutParameters.push({ flags, originalName: name, outParamName });
}
}
}
@@ -3432,7 +3729,7 @@ namespace ts {
function visitCallExpressionWithPotentialCapturedThisAssignment(node: CallExpression, assignToCapturedThis: boolean): CallExpression | BinaryExpression {
// We are here either because SuperKeyword was used somewhere in the expression, or
// because we contain a SpreadElementExpression.
if (node.transformFlags & TransformFlags.ContainsSpread ||
if (node.transformFlags & TransformFlags.ContainsRestOrSpread ||
node.expression.kind === SyntaxKind.SuperKeyword ||
isSuperProperty(skipOuterExpressions(node.expression))) {
@@ -3442,7 +3739,7 @@ namespace ts {
}
let resultingCall: CallExpression | BinaryExpression;
if (node.transformFlags & TransformFlags.ContainsSpread) {
if (node.transformFlags & TransformFlags.ContainsRestOrSpread) {
// [source]
// f(...a, b)
// x.m(...a, b)
@@ -3505,7 +3802,7 @@ namespace ts {
* @param node A NewExpression node.
*/
function visitNewExpression(node: NewExpression): LeftHandSideExpression {
if (node.transformFlags & TransformFlags.ContainsSpread) {
if (node.transformFlags & TransformFlags.ContainsRestOrSpread) {
// We are here because we contain a SpreadElementExpression.
// [source]
// new C(...a)
@@ -4079,7 +4376,7 @@ namespace ts {
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
}
};
return function (d, b) {
extendStatics(d, b);
+144 -14
View File
@@ -32,6 +32,15 @@ namespace ts {
let enclosingFunctionParameterNames: UnderscoreEscapedMap<true>;
/**
* Keeps track of property names accessed on super (`super.x`) within async functions.
*/
let capturedSuperProperties: UnderscoreEscapedMap<true>;
/** Whether the async function contains an element access on super (`super[x]`). */
let hasSuperElementAccess: boolean;
/** A set of node IDs for generated super accessors (variable statements). */
const substitutedSuperAccessors: boolean[] = [];
// Save the previous transformation hooks.
const previousOnEmitNode = context.onEmitNode;
const previousOnSubstituteNode = context.onSubstituteNode;
@@ -56,7 +65,6 @@ namespace ts {
if ((node.transformFlags & TransformFlags.ContainsES2017) === 0) {
return node;
}
switch (node.kind) {
case SyntaxKind.AsyncKeyword:
// ES2017 async modifier should be elided for targets < ES2017
@@ -77,6 +85,18 @@ namespace ts {
case SyntaxKind.ArrowFunction:
return visitArrowFunction(<ArrowFunction>node);
case SyntaxKind.PropertyAccessExpression:
if (capturedSuperProperties && isPropertyAccessExpression(node) && node.expression.kind === SyntaxKind.SuperKeyword) {
capturedSuperProperties.set(node.name.escapedText, true);
}
return visitEachChild(node, visitor, context);
case SyntaxKind.ElementAccessExpression:
if (capturedSuperProperties && (<ElementAccessExpression>node).expression.kind === SyntaxKind.SuperKeyword) {
hasSuperElementAccess = true;
}
return visitEachChild(node, visitor, context);
default:
return visitEachChild(node, visitor, context);
}
@@ -398,6 +418,11 @@ namespace ts {
recordDeclarationName(parameter, enclosingFunctionParameterNames);
}
const savedCapturedSuperProperties = capturedSuperProperties;
const savedHasSuperElementAccess = hasSuperElementAccess;
capturedSuperProperties = createUnderscoreEscapedMap<true>();
hasSuperElementAccess = false;
let result: ConciseBody;
if (!isArrowFunction) {
const statements: Statement[] = [];
@@ -415,18 +440,26 @@ namespace ts {
addStatementsAfterPrologue(statements, endLexicalEnvironment());
// Minor optimization, emit `_super` helper to capture `super` access in an arrow.
// This step isn't needed if we eventually transform this to ES5.
const emitSuperHelpers = languageVersion >= ScriptTarget.ES2015 && resolver.getNodeCheckFlags(node) & (NodeCheckFlags.AsyncMethodWithSuperBinding | NodeCheckFlags.AsyncMethodWithSuper);
if (emitSuperHelpers) {
enableSubstitutionForAsyncMethodsWithSuper();
const variableStatement = createSuperAccessVariableStatement(resolver, node, capturedSuperProperties);
substitutedSuperAccessors[getNodeId(variableStatement)] = true;
addStatementsAfterPrologue(statements, [variableStatement]);
}
const block = createBlock(statements, /*multiLine*/ true);
setTextRange(block, node.body);
// Minor optimization, emit `_super` helper to capture `super` access in an arrow.
// This step isn't needed if we eventually transform this to ES5.
if (languageVersion >= ScriptTarget.ES2015) {
if (emitSuperHelpers && hasSuperElementAccess) {
// Emit helpers for super element access expressions (`super[x]`).
if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuperBinding) {
enableSubstitutionForAsyncMethodsWithSuper();
addEmitHelper(block, advancedAsyncSuperHelper);
}
else if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuper) {
enableSubstitutionForAsyncMethodsWithSuper();
addEmitHelper(block, asyncSuperHelper);
}
}
@@ -452,6 +485,8 @@ namespace ts {
}
enclosingFunctionParameterNames = savedEnclosingFunctionParameterNames;
capturedSuperProperties = savedCapturedSuperProperties;
hasSuperElementAccess = savedHasSuperElementAccess;
return result;
}
@@ -493,6 +528,8 @@ namespace ts {
context.enableEmitNotification(SyntaxKind.GetAccessor);
context.enableEmitNotification(SyntaxKind.SetAccessor);
context.enableEmitNotification(SyntaxKind.Constructor);
// We need to be notified when entering the generated accessor arrow functions.
context.enableEmitNotification(SyntaxKind.VariableStatement);
}
}
@@ -516,6 +553,14 @@ namespace ts {
return;
}
}
// Disable substitution in the generated super accessor itself.
else if (enabledSubstitutions && substitutedSuperAccessors[getNodeId(node)]) {
const savedEnclosingSuperContainerFlags = enclosingSuperContainerFlags;
enclosingSuperContainerFlags = 0;
previousOnEmitNode(hint, node, emitCallback);
enclosingSuperContainerFlags = savedEnclosingSuperContainerFlags;
return;
}
previousOnEmitNode(hint, node, emitCallback);
}
@@ -548,8 +593,10 @@ namespace ts {
function substitutePropertyAccessExpression(node: PropertyAccessExpression) {
if (node.expression.kind === SyntaxKind.SuperKeyword) {
return createSuperAccessInAsyncMethod(
createLiteral(idText(node.name)),
return setTextRange(
createPropertyAccess(
createFileLevelUniqueName("_super"),
node.name),
node
);
}
@@ -558,7 +605,7 @@ namespace ts {
function substituteElementAccessExpression(node: ElementAccessExpression) {
if (node.expression.kind === SyntaxKind.SuperKeyword) {
return createSuperAccessInAsyncMethod(
return createSuperElementAccessInAsyncMethod(
node.argumentExpression,
node
);
@@ -593,12 +640,12 @@ namespace ts {
|| kind === SyntaxKind.SetAccessor;
}
function createSuperAccessInAsyncMethod(argumentExpression: Expression, location: TextRange): LeftHandSideExpression {
function createSuperElementAccessInAsyncMethod(argumentExpression: Expression, location: TextRange): LeftHandSideExpression {
if (enclosingSuperContainerFlags & NodeCheckFlags.AsyncMethodWithSuperBinding) {
return setTextRange(
createPropertyAccess(
createCall(
createFileLevelUniqueName("_super"),
createFileLevelUniqueName("_superIndex"),
/*typeArguments*/ undefined,
[argumentExpression]
),
@@ -610,7 +657,7 @@ namespace ts {
else {
return setTextRange(
createCall(
createFileLevelUniqueName("_super"),
createFileLevelUniqueName("_superIndex"),
/*typeArguments*/ undefined,
[argumentExpression]
),
@@ -620,6 +667,89 @@ namespace ts {
}
}
/** Creates a variable named `_super` with accessor properties for the given property names. */
export function createSuperAccessVariableStatement(resolver: EmitResolver, node: FunctionLikeDeclaration, names: UnderscoreEscapedMap<true>) {
// Create a variable declaration with a getter/setter (if binding) definition for each name:
// const _super = Object.create(null, { x: { get: () => super.x, set: (v) => super.x = v }, ... });
const hasBinding = (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuperBinding) !== 0;
const accessors: PropertyAssignment[] = [];
names.forEach((_, key) => {
const name = unescapeLeadingUnderscores(key);
const getterAndSetter: PropertyAssignment[] = [];
getterAndSetter.push(createPropertyAssignment(
"get",
createArrowFunction(
/* modifiers */ undefined,
/* typeParameters */ undefined,
/* parameters */ [],
/* type */ undefined,
/* equalsGreaterThanToken */ undefined,
createPropertyAccess(
createSuper(),
name
)
)
));
if (hasBinding) {
getterAndSetter.push(
createPropertyAssignment(
"set",
createArrowFunction(
/* modifiers */ undefined,
/* typeParameters */ undefined,
/* parameters */ [
createParameter(
/* decorators */ undefined,
/* modifiers */ undefined,
/* dotDotDotToken */ undefined,
"v",
/* questionToken */ undefined,
/* type */ undefined,
/* initializer */ undefined
)
],
/* type */ undefined,
/* equalsGreaterThanToken */ undefined,
createAssignment(
createPropertyAccess(
createSuper(),
name),
createIdentifier("v")
)
)
)
);
}
accessors.push(
createPropertyAssignment(
name,
createObjectLiteral(getterAndSetter),
)
);
});
return createVariableStatement(
/* modifiers */ undefined,
createVariableDeclarationList(
[
createVariableDeclaration(
createFileLevelUniqueName("_super"),
/* type */ undefined,
createCall(
createPropertyAccess(
createIdentifier("Object"),
"create"
),
/* typeArguments */ undefined,
[
createNull(),
createObjectLiteral(accessors, /* multiline */ true)
]
)
)
],
NodeFlags.Const));
}
const awaiterHelper: EmitHelper = {
name: "typescript:awaiter",
scoped: false,
@@ -667,14 +797,14 @@ namespace ts {
name: "typescript:async-super",
scoped: true,
text: helperString`
const ${"_super"} = name => super[name];`
const ${"_superIndex"} = name => super[name];`
};
export const advancedAsyncSuperHelper: EmitHelper = {
name: "typescript:advanced-async-super",
scoped: true,
text: helperString`
const ${"_super"} = (function (geti, seti) {
const ${"_superIndex"} = (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);`
+77 -33
View File
@@ -26,6 +26,13 @@ namespace ts {
let enclosingFunctionFlags: FunctionFlags;
let enclosingSuperContainerFlags: NodeCheckFlags = 0;
/** Keeps track of property names accessed on super (`super.x`) within async functions. */
let capturedSuperProperties: UnderscoreEscapedMap<true>;
/** Whether the async function contains an element access on super (`super[x]`). */
let hasSuperElementAccess: boolean;
/** A set of node IDs for generated super accessors. */
const substitutedSuperAccessors: boolean[] = [];
return chainBundle(transformSourceFile);
function transformSourceFile(node: SourceFile) {
@@ -57,7 +64,6 @@ namespace ts {
if ((node.transformFlags & TransformFlags.ContainsESNext) === 0) {
return node;
}
switch (node.kind) {
case SyntaxKind.AwaitExpression:
return visitAwaitExpression(node as AwaitExpression);
@@ -101,6 +107,16 @@ namespace ts {
return visitParenthesizedExpression(node as ParenthesizedExpression, noDestructuringValue);
case SyntaxKind.CatchClause:
return visitCatchClause(node as CatchClause);
case SyntaxKind.PropertyAccessExpression:
if (capturedSuperProperties && isPropertyAccessExpression(node) && node.expression.kind === SyntaxKind.SuperKeyword) {
capturedSuperProperties.set(node.name.escapedText, true);
}
return visitEachChild(node, visitor, context);
case SyntaxKind.ElementAccessExpression:
if (capturedSuperProperties && (<ElementAccessExpression>node).expression.kind === SyntaxKind.SuperKeyword) {
hasSuperElementAccess = true;
}
return visitEachChild(node, visitor, context);
default:
return visitEachChild(node, visitor, context);
}
@@ -210,7 +226,7 @@ namespace ts {
}
function visitObjectLiteralExpression(node: ObjectLiteralExpression): Expression {
if (node.transformFlags & TransformFlags.ContainsObjectSpread) {
if (node.transformFlags & TransformFlags.ContainsObjectRestOrSpread) {
// spread elements emit like so:
// non-spread elements are chunked together into object literals, and then all are passed to __assign:
// { a, ...o, b } => __assign({a}, o, {b});
@@ -250,7 +266,7 @@ namespace ts {
* @param node A BinaryExpression node.
*/
function visitBinaryExpression(node: BinaryExpression, noDestructuringValue: boolean): Expression {
if (isDestructuringAssignment(node) && node.left.transformFlags & TransformFlags.ContainsObjectRest) {
if (isDestructuringAssignment(node) && node.left.transformFlags & TransformFlags.ContainsObjectRestOrSpread) {
return flattenDestructuringAssignment(
node,
visitor,
@@ -276,7 +292,7 @@ namespace ts {
*/
function visitVariableDeclaration(node: VariableDeclaration): VisitResult<VariableDeclaration> {
// If we are here it is because the name contains a binding pattern with a rest somewhere in it.
if (isBindingPattern(node.name) && node.name.transformFlags & TransformFlags.ContainsObjectRest) {
if (isBindingPattern(node.name) && node.name.transformFlags & TransformFlags.ContainsObjectRestOrSpread) {
return flattenDestructuringBinding(
node,
visitor,
@@ -307,7 +323,7 @@ namespace ts {
* @param node A ForOfStatement.
*/
function visitForOfStatement(node: ForOfStatement, outermostLabeledStatement: LabeledStatement | undefined): VisitResult<Statement> {
if (node.initializer.transformFlags & TransformFlags.ContainsObjectRest) {
if (node.initializer.transformFlags & TransformFlags.ContainsObjectRestOrSpread) {
node = transformForOfStatementWithObjectRest(node);
}
if (node.awaitModifier) {
@@ -499,7 +515,7 @@ namespace ts {
}
function visitParameter(node: ParameterDeclaration): ParameterDeclaration {
if (node.transformFlags & TransformFlags.ContainsObjectRest) {
if (node.transformFlags & TransformFlags.ContainsObjectRestOrSpread) {
// Binding patterns are converted into a generated name and are
// evaluated inside the function body.
return updateParameter(
@@ -655,41 +671,57 @@ namespace ts {
const statementOffset = addPrologue(statements, node.body!.statements, /*ensureUseStrict*/ false, visitor);
appendObjectRestAssignmentsIfNeeded(statements, node);
statements.push(
createReturn(
createAsyncGeneratorHelper(
context,
createFunctionExpression(
/*modifiers*/ undefined,
createToken(SyntaxKind.AsteriskToken),
node.name && getGeneratedNameForNode(node.name),
/*typeParameters*/ undefined,
/*parameters*/ [],
/*type*/ undefined,
updateBlock(
node.body!,
visitLexicalEnvironment(node.body!.statements, visitor, context, statementOffset)
)
const savedCapturedSuperProperties = capturedSuperProperties;
const savedHasSuperElementAccess = hasSuperElementAccess;
capturedSuperProperties = createUnderscoreEscapedMap<true>();
hasSuperElementAccess = false;
const returnStatement = createReturn(
createAsyncGeneratorHelper(
context,
createFunctionExpression(
/*modifiers*/ undefined,
createToken(SyntaxKind.AsteriskToken),
node.name && getGeneratedNameForNode(node.name),
/*typeParameters*/ undefined,
/*parameters*/ [],
/*type*/ undefined,
updateBlock(
node.body!,
visitLexicalEnvironment(node.body!.statements, visitor, context, statementOffset)
)
)
)
);
// Minor optimization, emit `_super` helper to capture `super` access in an arrow.
// This step isn't needed if we eventually transform this to ES5.
const emitSuperHelpers = languageVersion >= ScriptTarget.ES2015 && resolver.getNodeCheckFlags(node) & (NodeCheckFlags.AsyncMethodWithSuperBinding | NodeCheckFlags.AsyncMethodWithSuper);
if (emitSuperHelpers) {
enableSubstitutionForAsyncMethodsWithSuper();
const variableStatement = createSuperAccessVariableStatement(resolver, node, capturedSuperProperties);
substitutedSuperAccessors[getNodeId(variableStatement)] = true;
addStatementsAfterPrologue(statements, [variableStatement]);
}
statements.push(returnStatement);
addStatementsAfterPrologue(statements, endLexicalEnvironment());
const block = updateBlock(node.body!, statements);
// Minor optimization, emit `_super` helper to capture `super` access in an arrow.
// This step isn't needed if we eventually transform this to ES5.
if (languageVersion >= ScriptTarget.ES2015) {
if (emitSuperHelpers && hasSuperElementAccess) {
if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuperBinding) {
enableSubstitutionForAsyncMethodsWithSuper();
addEmitHelper(block, advancedAsyncSuperHelper);
}
else if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuper) {
enableSubstitutionForAsyncMethodsWithSuper();
addEmitHelper(block, asyncSuperHelper);
}
}
capturedSuperProperties = savedCapturedSuperProperties;
hasSuperElementAccess = savedHasSuperElementAccess;
return block;
}
@@ -716,7 +748,7 @@ namespace ts {
function appendObjectRestAssignmentsIfNeeded(statements: Statement[] | undefined, node: FunctionLikeDeclaration): Statement[] | undefined {
for (const parameter of node.parameters) {
if (parameter.transformFlags & TransformFlags.ContainsObjectRest) {
if (parameter.transformFlags & TransformFlags.ContainsObjectRestOrSpread) {
const temp = getGeneratedNameForNode(parameter);
const declarations = flattenDestructuringBinding(
parameter,
@@ -758,6 +790,8 @@ namespace ts {
context.enableEmitNotification(SyntaxKind.GetAccessor);
context.enableEmitNotification(SyntaxKind.SetAccessor);
context.enableEmitNotification(SyntaxKind.Constructor);
// We need to be notified when entering the generated accessor arrow functions.
context.enableEmitNotification(SyntaxKind.VariableStatement);
}
}
@@ -781,6 +815,14 @@ namespace ts {
return;
}
}
// Disable substitution in the generated super accessor itself.
else if (enabledSubstitutions && substitutedSuperAccessors[getNodeId(node)]) {
const savedEnclosingSuperContainerFlags = enclosingSuperContainerFlags;
enclosingSuperContainerFlags = 0 as NodeCheckFlags;
previousOnEmitNode(hint, node, emitCallback);
enclosingSuperContainerFlags = savedEnclosingSuperContainerFlags;
return;
}
previousOnEmitNode(hint, node, emitCallback);
}
@@ -813,8 +855,10 @@ namespace ts {
function substitutePropertyAccessExpression(node: PropertyAccessExpression) {
if (node.expression.kind === SyntaxKind.SuperKeyword) {
return createSuperAccessInAsyncMethod(
createLiteral(idText(node.name)),
return setTextRange(
createPropertyAccess(
createFileLevelUniqueName("_super"),
node.name),
node
);
}
@@ -823,7 +867,7 @@ namespace ts {
function substituteElementAccessExpression(node: ElementAccessExpression) {
if (node.expression.kind === SyntaxKind.SuperKeyword) {
return createSuperAccessInAsyncMethod(
return createSuperElementAccessInAsyncMethod(
node.argumentExpression,
node
);
@@ -858,12 +902,12 @@ namespace ts {
|| kind === SyntaxKind.SetAccessor;
}
function createSuperAccessInAsyncMethod(argumentExpression: Expression, location: TextRange): LeftHandSideExpression {
function createSuperElementAccessInAsyncMethod(argumentExpression: Expression, location: TextRange): LeftHandSideExpression {
if (enclosingSuperContainerFlags & NodeCheckFlags.AsyncMethodWithSuperBinding) {
return setTextRange(
createPropertyAccess(
createCall(
createIdentifier("_super"),
createIdentifier("_superIndex"),
/*typeArguments*/ undefined,
[argumentExpression]
),
@@ -875,7 +919,7 @@ namespace ts {
else {
return setTextRange(
createCall(
createIdentifier("_super"),
createIdentifier("_superIndex"),
/*typeArguments*/ undefined,
[argumentExpression]
),
+4 -2
View File
@@ -973,9 +973,11 @@ namespace ts {
// Check if we have property assignment inside class declaration.
// If there is a property assignment, we need to emit constructor whether users define it or not
// If there is no property assignment, we can omit constructor if users do not define it
const hasInstancePropertyWithInitializer = forEach(node.members, isInstanceInitializedProperty);
const hasParameterPropertyAssignments = node.transformFlags & TransformFlags.ContainsParameterPropertyAssignments;
const constructor = getFirstConstructorWithBody(node);
const hasInstancePropertyWithInitializer = forEach(node.members, isInstanceInitializedProperty);
const hasParameterPropertyAssignments = constructor &&
constructor.transformFlags & TransformFlags.ContainsTypeScriptClassSyntax &&
forEach(constructor.parameters, isParameterWithPropertyAssignment);
// If the class does not contain nodes that require a synthesized constructor,
// accept the current constructor if it exists.
+481 -451
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -9,6 +9,7 @@
"files": [
"core.ts",
"performance.ts",
"semver.ts",
"types.ts",
"sys.ts",
@@ -51,6 +52,7 @@
"resolutionCache.ts",
"moduleSpecifiers.ts",
"watch.ts",
"tsbuild.ts"
"tsbuild.ts",
"inspectValue.ts",
]
}
+177 -69
View File
@@ -24,7 +24,84 @@ namespace ts {
| SyntaxKind.DotToken
| SyntaxKind.Identifier
| SyntaxKind.NoSubstitutionTemplateLiteral
| SyntaxKind.Unknown;
| SyntaxKind.Unknown
| KeywordSyntaxKind;
export type KeywordSyntaxKind =
| SyntaxKind.AbstractKeyword
| SyntaxKind.AnyKeyword
| SyntaxKind.AsKeyword
| SyntaxKind.BooleanKeyword
| SyntaxKind.BreakKeyword
| SyntaxKind.CaseKeyword
| SyntaxKind.CatchKeyword
| SyntaxKind.ClassKeyword
| SyntaxKind.ContinueKeyword
| SyntaxKind.ConstKeyword
| SyntaxKind.ConstructorKeyword
| SyntaxKind.DebuggerKeyword
| SyntaxKind.DeclareKeyword
| SyntaxKind.DefaultKeyword
| SyntaxKind.DeleteKeyword
| SyntaxKind.DoKeyword
| SyntaxKind.ElseKeyword
| SyntaxKind.EnumKeyword
| SyntaxKind.ExportKeyword
| SyntaxKind.ExtendsKeyword
| SyntaxKind.FalseKeyword
| SyntaxKind.FinallyKeyword
| SyntaxKind.ForKeyword
| SyntaxKind.FromKeyword
| SyntaxKind.FunctionKeyword
| SyntaxKind.GetKeyword
| SyntaxKind.IfKeyword
| SyntaxKind.ImplementsKeyword
| SyntaxKind.ImportKeyword
| SyntaxKind.InKeyword
| SyntaxKind.InferKeyword
| SyntaxKind.InstanceOfKeyword
| SyntaxKind.InterfaceKeyword
| SyntaxKind.IsKeyword
| SyntaxKind.KeyOfKeyword
| SyntaxKind.LetKeyword
| SyntaxKind.ModuleKeyword
| SyntaxKind.NamespaceKeyword
| SyntaxKind.NeverKeyword
| SyntaxKind.NewKeyword
| SyntaxKind.NullKeyword
| SyntaxKind.NumberKeyword
| SyntaxKind.ObjectKeyword
| SyntaxKind.PackageKeyword
| SyntaxKind.PrivateKeyword
| SyntaxKind.ProtectedKeyword
| SyntaxKind.PublicKeyword
| SyntaxKind.ReadonlyKeyword
| SyntaxKind.RequireKeyword
| SyntaxKind.GlobalKeyword
| SyntaxKind.ReturnKeyword
| SyntaxKind.SetKeyword
| SyntaxKind.StaticKeyword
| SyntaxKind.StringKeyword
| SyntaxKind.SuperKeyword
| SyntaxKind.SwitchKeyword
| SyntaxKind.SymbolKeyword
| SyntaxKind.ThisKeyword
| SyntaxKind.ThrowKeyword
| SyntaxKind.TrueKeyword
| SyntaxKind.TryKeyword
| SyntaxKind.TypeKeyword
| SyntaxKind.TypeOfKeyword
| SyntaxKind.UndefinedKeyword
| SyntaxKind.UniqueKeyword
| SyntaxKind.UnknownKeyword
| SyntaxKind.VarKeyword
| SyntaxKind.VoidKeyword
| SyntaxKind.WhileKeyword
| SyntaxKind.WithKeyword
| SyntaxKind.YieldKeyword
| SyntaxKind.AsyncKeyword
| SyntaxKind.AwaitKeyword
| SyntaxKind.OfKeyword;
export type JsxTokenSyntaxKind =
| SyntaxKind.LessThanSlashToken
@@ -1708,7 +1785,7 @@ namespace ts {
export interface ExpressionWithTypeArguments extends NodeWithTypeArguments {
kind: SyntaxKind.ExpressionWithTypeArguments;
parent: HeritageClause;
parent: HeritageClause | JSDocAugmentsTag;
expression: LeftHandSideExpression;
}
@@ -2551,7 +2628,18 @@ namespace ts {
fileName: string;
/* @internal */ path: Path;
text: string;
/** Resolved path can be different from path property,
* when file is included through project reference is mapped to its output instead of source
* in that case resolvedPath = path to output file
* path = input file's path
*/
/* @internal */ resolvedPath: Path;
/** Original file name that can be different from fileName,
* when file is included through project reference is mapped to its output instead of source
* in that case originalFileName = name of input file
* fileName = output file's name
*/
/* @internal */ originalFileName: string;
/**
* If two source files are for the same version of the same package, one will redirect to the other.
@@ -2593,6 +2681,8 @@ namespace ts {
/* @internal */ externalModuleIndicator?: Node;
// The first node that causes this file to be a CommonJS module
/* @internal */ commonJsModuleIndicator?: Node;
// JS identifier-declarations that are intended to merge with globals
/* @internal */ jsGlobalAugmentations?: SymbolTable;
/* @internal */ identifiers: Map<string>; // Map from a string to an interned string
/* @internal */ nodeCount: number;
@@ -2815,7 +2905,9 @@ namespace ts {
/* @internal */ getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string): ResolvedModuleWithFailedLookupLocations | undefined;
getProjectReferences(): (ResolvedProjectReference | undefined)[] | undefined;
getProjectReferences(): ReadonlyArray<ProjectReference> | undefined;
getResolvedProjectReferences(): (ResolvedProjectReference | undefined)[] | undefined;
/*@internal*/ getProjectReferenceRedirect(fileName: string): string | undefined;
}
/* @internal */
@@ -2893,7 +2985,7 @@ namespace ts {
}
/* @internal */
export interface TypeCheckerHost {
export interface TypeCheckerHost extends ModuleSpecifierResolutionHost {
getCompilerOptions(): CompilerOptions;
getSourceFiles(): ReadonlyArray<SourceFile>;
@@ -3011,8 +3103,6 @@ namespace ts {
getExportsOfModule(moduleSymbol: Symbol): Symbol[];
/** Unlike `getExportsOfModule`, this includes properties of an `export =` value. */
/* @internal */ getExportsAndPropertiesOfModule(moduleSymbol: Symbol): Symbol[];
getAllAttributesTypeFromJsxOpeningLikeElement(elementNode: JsxOpeningLikeElement): Type | undefined;
getJsxIntrinsicTagNamesAt(location: Node): Symbol[];
isOptionalParameter(node: ParameterDeclaration): boolean;
getAmbientModules(): Symbol[];
@@ -3034,8 +3124,8 @@ namespace ts {
/* @internal */ getStringType(): Type;
/* @internal */ getNumberType(): Type;
/* @internal */ getBooleanType(): Type;
/* @internal */ getFalseType(): Type;
/* @internal */ getTrueType(): Type;
/* @internal */ getFalseType(fresh?: boolean): Type;
/* @internal */ getTrueType(fresh?: boolean): Type;
/* @internal */ getVoidType(): Type;
/* @internal */ getUndefinedType(): Type;
/* @internal */ getNullType(): Type;
@@ -3079,7 +3169,7 @@ namespace ts {
* True if `contextualType` should not be considered for completions because
* e.g. it specifies `kind: "a"` and obj has `kind: "b"`.
*/
/* @internal */ isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression): boolean;
/* @internal */ isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean;
/**
* For a union, will include a property if it's defined in *any* of the member types.
* So for `{ a } | { b }`, this will include both `a` and `b`.
@@ -3168,6 +3258,8 @@ namespace ts {
InTypeAlias = 1 << 23, // Writing type in type alias declaration
InInitialEntityName = 1 << 24, // Set when writing the LHS of an entity name or entity name expression
InReverseMappedType = 1 << 25,
/* @internal */ DoNotIncludeSymbolChain = 1 << 26, // Skip looking up and printing an accessible symbol chain
}
// Ensure the shared flags between this and `NodeBuilderFlags` stay in alignment
@@ -3230,6 +3322,9 @@ namespace ts {
// Prefer aliases which are not directly visible
UseAliasDefinedOutsideCurrentScope = 0x00000008,
// Skip building an accessible symbol chain
/* @internal */ DoNotIncludeSymbolChain = 0x00000010,
}
/* @internal */
@@ -3383,7 +3478,7 @@ namespace ts {
isImplementationOfOverload(node: FunctionLike): boolean | undefined;
isRequiredInitializedParameter(node: ParameterDeclaration): boolean;
isOptionalUninitializedParameterProperty(node: ParameterDeclaration): boolean;
isJSContainerFunctionDeclaration(node: FunctionDeclaration): boolean;
isExpandoFunctionDeclaration(node: FunctionDeclaration): boolean;
getPropertiesOfContainerFunction(node: Declaration): Symbol[];
createTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration | PropertyAccessExpression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker, addUndefined?: boolean): TypeNode | undefined;
createReturnTypeOfSignatureDeclaration(signatureDeclaration: SignatureDeclaration, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker): TypeNode | undefined;
@@ -3405,6 +3500,7 @@ namespace ts {
getJsxFactoryEntity(location?: Node): EntityName | undefined;
getAllAccessorDeclarations(declaration: AccessorDeclaration): AllAccessorDeclarations;
getSymbolOfExternalModuleSpecifier(node: StringLiteralLike): Symbol | undefined;
isBindingCapturedByNode(node: Node, decl: VariableDeclaration | BindingElement): boolean;
}
export const enum SymbolFlags {
@@ -3435,7 +3531,7 @@ namespace ts {
ExportStar = 1 << 23, // Export * declaration
Optional = 1 << 24, // Optional property
Transient = 1 << 25, // Transient symbol (created during type check)
JSContainer = 1 << 26, // Contains Javascript special declarations
Assignment = 1 << 26, // Assignment treated as declaration (eg `this.prop = 1`)
ModuleExports = 1 << 27, // Symbol for CommonJS `module` of `module.exports`
/* @internal */
@@ -3444,8 +3540,8 @@ namespace ts {
Enum = RegularEnum | ConstEnum,
Variable = FunctionScopedVariable | BlockScopedVariable,
Value = Variable | Property | EnumMember | ObjectLiteral | Function | Class | Enum | ValueModule | Method | GetAccessor | SetAccessor | JSContainer,
Type = Class | Interface | Enum | EnumMember | TypeLiteral | TypeParameter | TypeAlias | JSContainer,
Value = Variable | Property | EnumMember | ObjectLiteral | Function | Class | Enum | ValueModule | Method | GetAccessor | SetAccessor | Assignment,
Type = Class | Interface | Enum | EnumMember | TypeLiteral | TypeParameter | TypeAlias | Assignment,
Namespace = ValueModule | NamespaceModule | Enum,
Module = ValueModule | NamespaceModule,
Accessor = GetAccessor | SetAccessor,
@@ -3466,7 +3562,7 @@ namespace ts {
InterfaceExcludes = Type & ~(Interface | Class),
RegularEnumExcludes = (Value | Type) & ~(RegularEnum | ValueModule), // regular enums merge only with regular enums and modules
ConstEnumExcludes = (Value | Type) & ~ConstEnum, // const enums merge only with const enums
ValueModuleExcludes = Value & ~(Function | Class | RegularEnum | ValueModule | JSContainer),
ValueModuleExcludes = Value & ~(Function | Class | RegularEnum | ValueModule | Assignment),
NamespaceModuleExcludes = 0,
MethodExcludes = Value & ~Method,
GetAccessorExcludes = Value & ~SetAccessor,
@@ -3520,6 +3616,7 @@ namespace ts {
type?: Type; // Type of value symbol
uniqueESSymbolType?: Type; // UniqueESSymbol type for a symbol
declaredType?: Type; // Type of class, interface, enum, type alias, or type parameter
resolvedJSDocType?: Type; // Resolved type of a JSDoc type reference
typeParameters?: TypeParameter[]; // Type parameters of type alias (undefined if non-generic)
outerTypeParameters?: TypeParameter[]; // Outer type parameters of anonymous object type
inferredClassType?: Type; // Type of an inferred ES5 class
@@ -3653,22 +3750,23 @@ namespace ts {
EnumValuesComputed = 0x00004000, // Values for enum members have been computed, and any errors have been reported for them.
LexicalModuleMergesWithClass = 0x00008000, // Instantiated lexical module declaration is merged with a previous class declaration.
LoopWithCapturedBlockScopedBinding = 0x00010000, // Loop that contains block scoped variable captured in closure
CapturedBlockScopedBinding = 0x00020000, // Block-scoped binding that is captured in some function
BlockScopedBindingInLoop = 0x00040000, // Block-scoped binding with declaration nested inside iteration statement
ClassWithBodyScopedClassBinding = 0x00080000, // Decorated class that contains a binding to itself inside of the class body.
BodyScopedClassBinding = 0x00100000, // Binding to a decorated class inside of the class's body.
NeedsLoopOutParameter = 0x00200000, // Block scoped binding whose value should be explicitly copied outside of the converted loop
AssignmentsMarked = 0x00400000, // Parameter assignments have been marked
ClassWithConstructorReference = 0x00800000, // Class that contains a binding to its constructor inside of the class body.
ConstructorReferenceInClass = 0x01000000, // Binding to a class constructor inside of the class's body.
ContainsCapturedBlockScopeBinding = 0x00020000, // Part of a loop that contains block scoped variable captured in closure
CapturedBlockScopedBinding = 0x00040000, // Block-scoped binding that is captured in some function
BlockScopedBindingInLoop = 0x00080000, // Block-scoped binding with declaration nested inside iteration statement
ClassWithBodyScopedClassBinding = 0x00100000, // Decorated class that contains a binding to itself inside of the class body.
BodyScopedClassBinding = 0x00200000, // Binding to a decorated class inside of the class's body.
NeedsLoopOutParameter = 0x00400000, // Block scoped binding whose value should be explicitly copied outside of the converted loop
AssignmentsMarked = 0x00800000, // Parameter assignments have been marked
ClassWithConstructorReference = 0x01000000, // Class that contains a binding to its constructor inside of the class body.
ConstructorReferenceInClass = 0x02000000, // Binding to a class constructor inside of the class's body.
}
/* @internal */
export interface NodeLinks {
flags: NodeCheckFlags; // Set of flags specific to Node
resolvedType?: Type; // Cached type of type node
resolvedEnumType?: Type; // Cached constraint type from enum jsdoc tag
resolvedSignature?: Signature; // Cached signature of signature node or call expression
resolvedSignatures?: Map<Signature[]>; // Cached signatures of jsx node
resolvedSymbol?: Symbol; // Cached name resolution result
resolvedIndexInfo?: IndexInfo; // Cached indexing info resolution result
maybeTypePredicate?: boolean; // Cached check whether call expression might reference a type predicate
@@ -3684,6 +3782,8 @@ namespace ts {
switchTypes?: Type[]; // Cached array of switch case expression types
jsxNamespace?: Symbol | false; // Resolved jsx namespace symbol for this node
contextFreeType?: Type; // Cached context-free type used by the first pass of inference; used when a function's return is partially contextually sensitive
deferredNodes?: Map<Node>; // Set of nodes whose checking has been deferred
capturedBlockScopeBindings?: Symbol[]; // Block-scoped bindings captured beneath this part of an IterationStatement
}
export const enum TypeFlags {
@@ -3731,7 +3831,7 @@ namespace ts {
Unit = Literal | UniqueESSymbol | Nullable,
StringOrNumberLiteral = StringLiteral | NumberLiteral,
/* @internal */
StringOrNumberLiteralOrUnique = StringOrNumberLiteral | UniqueESSymbol,
StringOrNumberLiteralOrUnique = StringLiteral | NumberLiteral | UniqueESSymbol,
/* @internal */
DefinitelyFalsy = StringLiteral | NumberLiteral | BooleanLiteral | Void | Undefined | Null,
PossiblyFalsy = DefinitelyFalsy | String | Number | Boolean,
@@ -3802,6 +3902,15 @@ namespace ts {
intrinsicName: string; // Name of intrinsic type
}
/* @internal */
export interface FreshableIntrinsicType extends IntrinsicType {
freshType: IntrinsicType; // Fresh version of type
regularType: IntrinsicType; // Regular version of type
}
/* @internal */
export type FreshableType = LiteralType | FreshableIntrinsicType;
// String literal types (TypeFlags.StringLiteral)
// Numeric literal types (TypeFlags.NumberLiteral)
export interface LiteralType extends Type {
@@ -3962,6 +4071,7 @@ namespace ts {
templateType?: Type;
modifiersType?: Type;
resolvedApparentType?: Type;
instantiating?: boolean;
}
export interface EvolvingArrayType extends ObjectType {
@@ -4219,7 +4329,7 @@ namespace ts {
}
/* @internal */
export const enum SpecialPropertyAssignmentKind {
export const enum AssignmentDeclarationKind {
None,
/// exports.name = expr
ExportsProperty,
@@ -4402,6 +4512,7 @@ namespace ts {
sourceRoot?: string;
strict?: boolean;
strictFunctionTypes?: boolean; // Always combine with strict property
strictBindCallApply?: boolean; // Always combine with strict property
strictNullChecks?: boolean; // Always combine with strict property
strictPropertyInitialization?: boolean; // Always combine with strict property
stripInternal?: boolean;
@@ -4516,7 +4627,6 @@ namespace ts {
/* @internal */
export interface ConfigFileSpecs {
filesSpecs: ReadonlyArray<string> | undefined;
referencesSpecs: ReadonlyArray<ProjectReference> | undefined;
/**
* Present to report errors (user specified specs), validatedIncludeSpecs are used for file name matching
*/
@@ -4532,7 +4642,6 @@ namespace ts {
export interface ExpandResult {
fileNames: string[];
projectReferences: ReadonlyArray<ProjectReference> | undefined;
wildcardDirectories: MapLike<WatchDirectoryFlags>;
/* @internal */ spec: ConfigFileSpecs;
}
@@ -4559,6 +4668,9 @@ namespace ts {
showInSimplifiedHelpView?: boolean;
category?: DiagnosticMessage;
strictFlag?: true; // true if the option is one of the flag under strict
affectsSourceFile?: true; // true if we should recreate SourceFiles after this option changes
affectsModuleResolution?: true; // currently same effect as `affectsSourceFile`
affectsBindDiagnostics?: true; // true if this affects binding (currently same effect as `affectsSourceFile`)
affectsSemanticDiagnostics?: true; // true if option affects semantic diagnostics
}
@@ -4878,25 +4990,21 @@ namespace ts {
// Markers
// - Flags used to indicate that a subtree contains a specific transformation.
ContainsDecorators = 1 << 12,
ContainsPropertyInitializer = 1 << 13,
ContainsLexicalThis = 1 << 14,
ContainsCapturedLexicalThis = 1 << 15,
ContainsLexicalThisInComputedPropertyName = 1 << 16,
ContainsDefaultValueAssignments = 1 << 17,
ContainsParameterPropertyAssignments = 1 << 18,
ContainsSpread = 1 << 19,
ContainsObjectSpread = 1 << 20,
ContainsRest = ContainsSpread,
ContainsObjectRest = ContainsObjectSpread,
ContainsComputedPropertyName = 1 << 21,
ContainsBlockScopedBinding = 1 << 22,
ContainsBindingPattern = 1 << 23,
ContainsYield = 1 << 24,
ContainsHoistedDeclarationOrCompletion = 1 << 25,
ContainsDynamicImport = 1 << 26,
Super = 1 << 27,
ContainsSuper = 1 << 28,
ContainsTypeScriptClassSyntax = 1 << 12, // Decorators, Property Initializers, Parameter Property Initializers
ContainsLexicalThis = 1 << 13,
ContainsCapturedLexicalThis = 1 << 14,
ContainsLexicalThisInComputedPropertyName = 1 << 15,
ContainsDefaultValueAssignments = 1 << 16,
ContainsRestOrSpread = 1 << 17,
ContainsObjectRestOrSpread = 1 << 18,
ContainsComputedPropertyName = 1 << 19,
ContainsBlockScopedBinding = 1 << 20,
ContainsBindingPattern = 1 << 21,
ContainsYield = 1 << 22,
ContainsHoistedDeclarationOrCompletion = 1 << 23,
ContainsDynamicImport = 1 << 24,
Super = 1 << 25,
ContainsSuper = 1 << 26,
// Please leave this as 1 << 29.
// It is the maximum bit we can set before we outgrow the size of a v8 small integer (SMI) on an x86 system.
@@ -4920,23 +5028,22 @@ namespace ts {
OuterExpressionExcludes = TypeScript | ES2015 | DestructuringAssignment | Generator | HasComputedFlags,
PropertyAccessExcludes = OuterExpressionExcludes | Super,
NodeExcludes = PropertyAccessExcludes | ContainsSuper,
ArrowFunctionExcludes = NodeExcludes | ContainsDecorators | ContainsDefaultValueAssignments | ContainsLexicalThis | ContainsParameterPropertyAssignments | ContainsBlockScopedBinding | ContainsYield | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRest,
FunctionExcludes = NodeExcludes | ContainsDecorators | ContainsDefaultValueAssignments | ContainsCapturedLexicalThis | ContainsLexicalThis | ContainsParameterPropertyAssignments | ContainsBlockScopedBinding | ContainsYield | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRest,
ConstructorExcludes = NodeExcludes | ContainsDefaultValueAssignments | ContainsLexicalThis | ContainsCapturedLexicalThis | ContainsBlockScopedBinding | ContainsYield | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRest,
MethodOrAccessorExcludes = NodeExcludes | ContainsDefaultValueAssignments | ContainsLexicalThis | ContainsCapturedLexicalThis | ContainsBlockScopedBinding | ContainsYield | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRest,
ClassExcludes = NodeExcludes | ContainsDecorators | ContainsPropertyInitializer | ContainsLexicalThis | ContainsCapturedLexicalThis | ContainsComputedPropertyName | ContainsParameterPropertyAssignments | ContainsLexicalThisInComputedPropertyName,
ModuleExcludes = NodeExcludes | ContainsDecorators | ContainsLexicalThis | ContainsCapturedLexicalThis | ContainsBlockScopedBinding | ContainsHoistedDeclarationOrCompletion,
ArrowFunctionExcludes = NodeExcludes | ContainsTypeScriptClassSyntax | ContainsDefaultValueAssignments | ContainsLexicalThis | ContainsBlockScopedBinding | ContainsYield | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRestOrSpread,
FunctionExcludes = NodeExcludes | ContainsTypeScriptClassSyntax | ContainsDefaultValueAssignments | ContainsCapturedLexicalThis | ContainsLexicalThis | ContainsBlockScopedBinding | ContainsYield | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRestOrSpread,
ConstructorExcludes = NodeExcludes | ContainsDefaultValueAssignments | ContainsLexicalThis | ContainsCapturedLexicalThis | ContainsBlockScopedBinding | ContainsYield | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRestOrSpread,
MethodOrAccessorExcludes = NodeExcludes | ContainsDefaultValueAssignments | ContainsLexicalThis | ContainsCapturedLexicalThis | ContainsBlockScopedBinding | ContainsYield | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRestOrSpread,
ClassExcludes = NodeExcludes | ContainsTypeScriptClassSyntax | ContainsLexicalThis | ContainsCapturedLexicalThis | ContainsComputedPropertyName | ContainsLexicalThisInComputedPropertyName,
ModuleExcludes = NodeExcludes | ContainsTypeScriptClassSyntax | ContainsLexicalThis | ContainsCapturedLexicalThis | ContainsBlockScopedBinding | ContainsHoistedDeclarationOrCompletion,
TypeExcludes = ~ContainsTypeScript,
ObjectLiteralExcludes = NodeExcludes | ContainsDecorators | ContainsComputedPropertyName | ContainsLexicalThisInComputedPropertyName | ContainsObjectSpread,
ArrayLiteralOrCallOrNewExcludes = NodeExcludes | ContainsSpread,
VariableDeclarationListExcludes = NodeExcludes | ContainsBindingPattern | ContainsObjectRest,
ObjectLiteralExcludes = NodeExcludes | ContainsTypeScriptClassSyntax | ContainsComputedPropertyName | ContainsLexicalThisInComputedPropertyName | ContainsObjectRestOrSpread,
ArrayLiteralOrCallOrNewExcludes = NodeExcludes | ContainsRestOrSpread,
VariableDeclarationListExcludes = NodeExcludes | ContainsBindingPattern | ContainsObjectRestOrSpread,
ParameterExcludes = NodeExcludes,
CatchClauseExcludes = NodeExcludes | ContainsObjectRest,
BindingPatternExcludes = NodeExcludes | ContainsRest,
CatchClauseExcludes = NodeExcludes | ContainsObjectRestOrSpread,
BindingPatternExcludes = NodeExcludes | ContainsRestOrSpread,
// Masks
// - Additional bitmasks
TypeScriptClassSyntaxMask = ContainsParameterPropertyAssignments | ContainsPropertyInitializer | ContainsDecorators,
ES2015FunctionSyntaxMask = ContainsCapturedLexicalThis | ContainsDefaultValueAssignments,
}
@@ -4954,7 +5061,7 @@ namespace ts {
/* @internal */
export interface EmitNode {
annotatedNodes?: Node[]; // Tracks Parse-tree nodes with EmitNodes for eventual cleanup.
flags: EmitFlags; // Flags that customize emit
flags: EmitFlags; // Flags that customize emit
leadingComments?: SynthesizedComment[]; // Synthesized leading comments
trailingComments?: SynthesizedComment[]; // Synthesized trailing comments
commentRange?: TextRange; // The text range to use when emitting leading or trailing comments
@@ -5314,6 +5421,7 @@ namespace ts {
/*@internal*/ inlineSourceMap?: boolean;
/*@internal*/ extendedDiagnostics?: boolean;
/*@internal*/ onlyPrintJsDocStyle?: boolean;
/*@internal*/ neverAsciiEscape?: boolean;
}
/* @internal */
@@ -5351,7 +5459,7 @@ namespace ts {
reportInaccessibleThisError?(): void;
reportPrivateInBaseOfClassExpression?(propertyName: string): void;
reportInaccessibleUniqueSymbolError?(): void;
moduleResolverHost?: EmitHost;
moduleResolverHost?: ModuleSpecifierResolutionHost & { getSourceFiles(): ReadonlyArray<SourceFile>, getCommonSourceDirectory(): string };
trackReferencedAmbientModule?(decl: ModuleDeclaration, symbol: Symbol): void;
trackExternalModuleSymbolOfImportTypeNode?(symbol: Symbol): void;
}
@@ -5585,15 +5693,15 @@ namespace ts {
type ConcretePragmaSpecs = typeof commentPragmas;
/* @internal */
export type PragmaPsuedoMap = {[K in keyof ConcretePragmaSpecs]?: {arguments: PragmaArgumentType<ConcretePragmaSpecs[K]>, range: CommentRange}};
export type PragmaPseudoMap = {[K in keyof ConcretePragmaSpecs]?: {arguments: PragmaArgumentType<ConcretePragmaSpecs[K]>, range: CommentRange}};
/* @internal */
export type PragmaPsuedoMapEntry = {[K in keyof PragmaPsuedoMap]: {name: K, args: PragmaPsuedoMap[K]}}[keyof PragmaPsuedoMap];
export type PragmaPseudoMapEntry = {[K in keyof PragmaPseudoMap]: {name: K, args: PragmaPseudoMap[K]}}[keyof PragmaPseudoMap];
/* @internal */
export interface ReadonlyPragmaMap extends ReadonlyMap<PragmaPsuedoMap[keyof PragmaPsuedoMap] | PragmaPsuedoMap[keyof PragmaPsuedoMap][]> {
get<TKey extends keyof PragmaPsuedoMap>(key: TKey): PragmaPsuedoMap[TKey] | PragmaPsuedoMap[TKey][];
forEach(action: <TKey extends keyof PragmaPsuedoMap>(value: PragmaPsuedoMap[TKey] | PragmaPsuedoMap[TKey][], key: TKey) => void): void;
export interface ReadonlyPragmaMap extends ReadonlyMap<PragmaPseudoMap[keyof PragmaPseudoMap] | PragmaPseudoMap[keyof PragmaPseudoMap][]> {
get<TKey extends keyof PragmaPseudoMap>(key: TKey): PragmaPseudoMap[TKey] | PragmaPseudoMap[TKey][];
forEach(action: <TKey extends keyof PragmaPseudoMap>(value: PragmaPseudoMap[TKey] | PragmaPseudoMap[TKey][], key: TKey) => void): void;
}
/**
@@ -5602,10 +5710,10 @@ namespace ts {
* in multiple places
*/
/* @internal */
export interface PragmaMap extends Map<PragmaPsuedoMap[keyof PragmaPsuedoMap] | PragmaPsuedoMap[keyof PragmaPsuedoMap][]>, ReadonlyPragmaMap {
set<TKey extends keyof PragmaPsuedoMap>(key: TKey, value: PragmaPsuedoMap[TKey] | PragmaPsuedoMap[TKey][]): this;
get<TKey extends keyof PragmaPsuedoMap>(key: TKey): PragmaPsuedoMap[TKey] | PragmaPsuedoMap[TKey][];
forEach(action: <TKey extends keyof PragmaPsuedoMap>(value: PragmaPsuedoMap[TKey] | PragmaPsuedoMap[TKey][], key: TKey) => void): void;
export interface PragmaMap extends Map<PragmaPseudoMap[keyof PragmaPseudoMap] | PragmaPseudoMap[keyof PragmaPseudoMap][]>, ReadonlyPragmaMap {
set<TKey extends keyof PragmaPseudoMap>(key: TKey, value: PragmaPseudoMap[TKey] | PragmaPseudoMap[TKey][]): this;
get<TKey extends keyof PragmaPseudoMap>(key: TKey): PragmaPseudoMap[TKey] | PragmaPseudoMap[TKey][];
forEach(action: <TKey extends keyof PragmaPseudoMap>(value: PragmaPseudoMap[TKey] | PragmaPseudoMap[TKey][], key: TKey) => void): void;
}
export interface UserPreferences {
+172 -125
View File
@@ -14,7 +14,6 @@ namespace ts {
/* @internal */
namespace ts {
export const emptyArray: never[] = [] as never[];
export const resolvingEmptyArray: never[] = [] as never[];
export const emptyMap = createMap<never>() as ReadonlyMap<never> & ReadonlyPragmaMap;
export const emptyUnderscoreEscapedMap: ReadonlyUnderscoreEscapedMap<never> = emptyMap as ReadonlyUnderscoreEscapedMap<never>;
@@ -102,22 +101,8 @@ namespace ts {
}
export function changesAffectModuleResolution(oldOptions: CompilerOptions, newOptions: CompilerOptions): boolean {
return !oldOptions ||
(oldOptions.module !== newOptions.module) ||
(oldOptions.moduleResolution !== newOptions.moduleResolution) ||
(oldOptions.noResolve !== newOptions.noResolve) ||
(oldOptions.target !== newOptions.target) ||
(oldOptions.noLib !== newOptions.noLib) ||
(oldOptions.jsx !== newOptions.jsx) ||
(oldOptions.allowJs !== newOptions.allowJs) ||
(oldOptions.rootDir !== newOptions.rootDir) ||
(oldOptions.configFilePath !== newOptions.configFilePath) ||
(oldOptions.baseUrl !== newOptions.baseUrl) ||
(oldOptions.maxNodeModuleJsDepth !== newOptions.maxNodeModuleJsDepth) ||
!arrayIsEqualTo(oldOptions.lib, newOptions.lib) ||
!arrayIsEqualTo(oldOptions.typeRoots, newOptions.typeRoots) ||
!arrayIsEqualTo(oldOptions.rootDirs, newOptions.rootDirs) ||
!equalOwnProperties(oldOptions.paths, newOptions.paths);
return oldOptions.configFilePath !== newOptions.configFilePath || moduleResolutionOptionDeclarations.some(o =>
!isJsonEqual(getCompilerOptionValue(oldOptions, o), getCompilerOptionValue(newOptions, o)));
}
/**
@@ -250,6 +235,12 @@ namespace ts {
sourceFile.resolvedTypeReferenceDirectiveNames.set(typeReferenceDirectiveName, resolvedTypeReferenceDirective);
}
export function projectReferenceIsEqualTo(oldRef: ProjectReference, newRef: ProjectReference) {
return oldRef.path === newRef.path &&
!oldRef.prepend === !newRef.prepend &&
!oldRef.circular === !newRef.circular;
}
export function moduleResolutionIsEqualTo(oldResolution: ResolvedModuleFull, newResolution: ResolvedModuleFull): boolean {
return oldResolution.isExternalLibraryImport === newResolution.isExternalLibraryImport &&
oldResolution.extension === newResolution.extension &&
@@ -533,14 +524,14 @@ namespace ts {
return emitNode && emitNode.flags || 0;
}
export function getLiteralText(node: LiteralLikeNode, sourceFile: SourceFile) {
export function getLiteralText(node: LiteralLikeNode, sourceFile: SourceFile, neverAsciiEscape: boolean | undefined) {
// If we don't need to downlevel and we can reach the original source text using
// the node's parent reference, then simply get the text as it was originally written.
if (!nodeIsSynthesized(node) && node.parent && !(isNumericLiteral(node) && node.numericLiteralFlags & TokenFlags.ContainsSeparator)) {
return getSourceTextOfNodeFromSourceFile(sourceFile, node);
}
const escapeText = getEmitFlags(node) & EmitFlags.NoAsciiEscaping ? escapeString : escapeNonAsciiString;
const escapeText = neverAsciiEscape || (getEmitFlags(node) & EmitFlags.NoAsciiEscaping) ? escapeString : escapeNonAsciiString;
// If we can't reach the original source text, use the canonical form if it's a number,
// or a (possibly escaped) quoted form of the original text if it's string-like.
@@ -987,6 +978,7 @@ namespace ts {
case SyntaxKind.StringKeyword:
case SyntaxKind.BooleanKeyword:
case SyntaxKind.SymbolKeyword:
case SyntaxKind.ObjectKeyword:
case SyntaxKind.UndefinedKeyword:
case SyntaxKind.NeverKeyword:
return true;
@@ -1679,15 +1671,15 @@ namespace ts {
return node.kind === SyntaxKind.ImportEqualsDeclaration && (<ImportEqualsDeclaration>node).moduleReference.kind !== SyntaxKind.ExternalModuleReference;
}
export function isSourceFileJavaScript(file: SourceFile): boolean {
return isInJavaScriptFile(file);
export function isSourceFileJS(file: SourceFile): boolean {
return isInJSFile(file);
}
export function isSourceFileNotJavaScript(file: SourceFile): boolean {
return !isInJavaScriptFile(file);
export function isSourceFileNotJS(file: SourceFile): boolean {
return !isInJSFile(file);
}
export function isInJavaScriptFile(node: Node | undefined): boolean {
export function isInJSFile(node: Node | undefined): boolean {
return !!node && !!(node.flags & NodeFlags.JavaScriptFile);
}
@@ -1739,14 +1731,14 @@ namespace ts {
return getSourceTextOfNodeFromSourceFile(sourceFile, str).charCodeAt(0) === CharacterCodes.doubleQuote;
}
export function getDeclarationOfJSInitializer(node: Node): Node | undefined {
export function getDeclarationOfExpando(node: Node): Node | undefined {
if (!node.parent) {
return undefined;
}
let name: Expression | BindingName | undefined;
let decl: Node | undefined;
if (isVariableDeclaration(node.parent) && node.parent.initializer === node) {
if (!isInJavaScriptFile(node) && !isVarConst(node.parent)) {
if (!isInJSFile(node) && !isVarConst(node.parent)) {
return undefined;
}
name = node.parent.name;
@@ -1771,7 +1763,7 @@ namespace ts {
}
}
if (!name || !getJavascriptInitializer(node, isPrototypeAccess(name))) {
if (!name || !getExpandoInitializer(node, isPrototypeAccess(name))) {
return undefined;
}
return decl;
@@ -1783,7 +1775,7 @@ namespace ts {
/** Get the initializer, taking into account defaulted Javascript initializers */
export function getEffectiveInitializer(node: HasExpressionInitializer) {
if (isInJavaScriptFile(node) && node.initializer &&
if (isInJSFile(node) && node.initializer &&
isBinaryExpression(node.initializer) && node.initializer.operatorToken.kind === SyntaxKind.BarBarToken &&
node.name && isEntityNameExpression(node.name) && isSameEntityName(node.name, node.initializer.left)) {
return node.initializer.right;
@@ -1791,26 +1783,26 @@ namespace ts {
return node.initializer;
}
/** Get the declaration initializer when it is container-like (See getJavascriptInitializer). */
export function getDeclaredJavascriptInitializer(node: HasExpressionInitializer) {
/** Get the declaration initializer when it is container-like (See getExpandoInitializer). */
export function getDeclaredExpandoInitializer(node: HasExpressionInitializer) {
const init = getEffectiveInitializer(node);
return init && getJavascriptInitializer(init, isPrototypeAccess(node.name));
return init && getExpandoInitializer(init, isPrototypeAccess(node.name));
}
/**
* Get the assignment 'initializer' -- the righthand side-- when the initializer is container-like (See getJavascriptInitializer).
* Get the assignment 'initializer' -- the righthand side-- when the initializer is container-like (See getExpandoInitializer).
* We treat the right hand side of assignments with container-like initalizers as declarations.
*/
export function getAssignedJavascriptInitializer(node: Node) {
export function getAssignedExpandoInitializer(node: Node) {
if (node && node.parent && isBinaryExpression(node.parent) && node.parent.operatorToken.kind === SyntaxKind.EqualsToken) {
const isPrototypeAssignment = isPrototypeAccess(node.parent.left);
return getJavascriptInitializer(node.parent.right, isPrototypeAssignment) ||
getDefaultedJavascriptInitializer(node.parent.left as EntityNameExpression, node.parent.right, isPrototypeAssignment);
return getExpandoInitializer(node.parent.right, isPrototypeAssignment) ||
getDefaultedExpandoInitializer(node.parent.left as EntityNameExpression, node.parent.right, isPrototypeAssignment);
}
}
/**
* Recognized Javascript container-like initializers are:
* Recognized expando initializers are:
* 1. (function() {})() -- IIFEs
* 2. function() { } -- Function expressions
* 3. class { } -- Class expressions
@@ -1819,7 +1811,7 @@ namespace ts {
*
* This function returns the provided initializer, or undefined if it is not valid.
*/
export function getJavascriptInitializer(initializer: Node, isPrototypeAssignment: boolean): Expression | undefined {
export function getExpandoInitializer(initializer: Node, isPrototypeAssignment: boolean): Expression | undefined {
if (isCallExpression(initializer)) {
const e = skipParentheses(initializer.expression);
return e.kind === SyntaxKind.FunctionExpression || e.kind === SyntaxKind.ArrowFunction ? initializer : undefined;
@@ -1835,29 +1827,29 @@ namespace ts {
}
/**
* A defaulted Javascript initializer matches the pattern
* `Lhs = Lhs || JavascriptInitializer`
* or `var Lhs = Lhs || JavascriptInitializer`
* A defaulted expando initializer matches the pattern
* `Lhs = Lhs || ExpandoInitializer`
* or `var Lhs = Lhs || ExpandoInitializer`
*
* The second Lhs is required to be the same as the first except that it may be prefixed with
* 'window.', 'global.' or 'self.' The second Lhs is otherwise ignored by the binder and checker.
*/
function getDefaultedJavascriptInitializer(name: EntityNameExpression, initializer: Expression, isPrototypeAssignment: boolean) {
const e = isBinaryExpression(initializer) && initializer.operatorToken.kind === SyntaxKind.BarBarToken && getJavascriptInitializer(initializer.right, isPrototypeAssignment);
function getDefaultedExpandoInitializer(name: EntityNameExpression, initializer: Expression, isPrototypeAssignment: boolean) {
const e = isBinaryExpression(initializer) && initializer.operatorToken.kind === SyntaxKind.BarBarToken && getExpandoInitializer(initializer.right, isPrototypeAssignment);
if (e && isSameEntityName(name, (initializer as BinaryExpression).left as EntityNameExpression)) {
return e;
}
}
export function isDefaultedJavascriptInitializer(node: BinaryExpression) {
export function isDefaultedExpandoInitializer(node: BinaryExpression) {
const name = isVariableDeclaration(node.parent) ? node.parent.name :
isBinaryExpression(node.parent) && node.parent.operatorToken.kind === SyntaxKind.EqualsToken ? node.parent.left :
undefined;
return name && getJavascriptInitializer(node.right, isPrototypeAccess(name)) && isEntityNameExpression(name) && isSameEntityName(name, node.left);
return name && getExpandoInitializer(node.right, isPrototypeAccess(name)) && isEntityNameExpression(name) && isSameEntityName(name, node.left);
}
/** Given a Javascript initializer, return the outer name. That is, the lhs of the assignment or the declaration name. */
export function getOuterNameOfJsInitializer(node: Declaration): DeclarationName | undefined {
/** Given an expando initializer, return its declaration name, or the left-hand side of the assignment if it's part of an assignment declaration. */
export function getNameOfExpando(node: Declaration): DeclarationName | undefined {
if (isBinaryExpression(node.parent)) {
const parent = (node.parent.operatorToken.kind === SyntaxKind.BarBarToken && isBinaryExpression(node.parent.parent)) ? node.parent.parent : node.parent;
if (parent.operatorToken.kind === SyntaxKind.EqualsToken && isIdentifier(parent.left)) {
@@ -1913,36 +1905,36 @@ namespace ts {
/// Given a BinaryExpression, returns SpecialPropertyAssignmentKind for the various kinds of property
/// assignments we treat as special in the binder
export function getSpecialPropertyAssignmentKind(expr: BinaryExpression): SpecialPropertyAssignmentKind {
const special = getSpecialPropertyAssignmentKindWorker(expr);
return special === SpecialPropertyAssignmentKind.Property || isInJavaScriptFile(expr) ? special : SpecialPropertyAssignmentKind.None;
export function getAssignmentDeclarationKind(expr: BinaryExpression): AssignmentDeclarationKind {
const special = getAssignmentDeclarationKindWorker(expr);
return special === AssignmentDeclarationKind.Property || isInJSFile(expr) ? special : AssignmentDeclarationKind.None;
}
function getSpecialPropertyAssignmentKindWorker(expr: BinaryExpression): SpecialPropertyAssignmentKind {
function getAssignmentDeclarationKindWorker(expr: BinaryExpression): AssignmentDeclarationKind {
if (expr.operatorToken.kind !== SyntaxKind.EqualsToken ||
!isPropertyAccessExpression(expr.left)) {
return SpecialPropertyAssignmentKind.None;
return AssignmentDeclarationKind.None;
}
const lhs = expr.left;
if (isEntityNameExpression(lhs.expression) && lhs.name.escapedText === "prototype" && isObjectLiteralExpression(getInitializerOfBinaryExpression(expr))) {
// F.prototype = { ... }
return SpecialPropertyAssignmentKind.Prototype;
return AssignmentDeclarationKind.Prototype;
}
return getSpecialPropertyAccessKind(lhs);
return getAssignmentDeclarationPropertyAccessKind(lhs);
}
export function getSpecialPropertyAccessKind(lhs: PropertyAccessExpression): SpecialPropertyAssignmentKind {
export function getAssignmentDeclarationPropertyAccessKind(lhs: PropertyAccessExpression): AssignmentDeclarationKind {
if (lhs.expression.kind === SyntaxKind.ThisKeyword) {
return SpecialPropertyAssignmentKind.ThisProperty;
return AssignmentDeclarationKind.ThisProperty;
}
else if (isIdentifier(lhs.expression) && lhs.expression.escapedText === "module" && lhs.name.escapedText === "exports") {
// module.exports = expr
return SpecialPropertyAssignmentKind.ModuleExports;
return AssignmentDeclarationKind.ModuleExports;
}
else if (isEntityNameExpression(lhs.expression)) {
if (isPrototypeAccess(lhs.expression)) {
// F.G....prototype.x = expr
return SpecialPropertyAssignmentKind.PrototypeProperty;
return AssignmentDeclarationKind.PrototypeProperty;
}
let nextToLast = lhs;
@@ -1954,13 +1946,13 @@ namespace ts {
if (id.escapedText === "exports" ||
id.escapedText === "module" && nextToLast.name.escapedText === "exports") {
// exports.name = expr OR module.exports.name = expr
return SpecialPropertyAssignmentKind.ExportsProperty;
return AssignmentDeclarationKind.ExportsProperty;
}
// F.G...x = expr
return SpecialPropertyAssignmentKind.Property;
return AssignmentDeclarationKind.Property;
}
return SpecialPropertyAssignmentKind.None;
return AssignmentDeclarationKind.None;
}
export function getInitializerOfBinaryExpression(expr: BinaryExpression) {
@@ -1971,11 +1963,11 @@ namespace ts {
}
export function isPrototypePropertyAssignment(node: Node): boolean {
return isBinaryExpression(node) && getSpecialPropertyAssignmentKind(node) === SpecialPropertyAssignmentKind.PrototypeProperty;
return isBinaryExpression(node) && getAssignmentDeclarationKind(node) === AssignmentDeclarationKind.PrototypeProperty;
}
export function isSpecialPropertyDeclaration(expr: PropertyAccessExpression): boolean {
return isInJavaScriptFile(expr) &&
return isInJSFile(expr) &&
expr.parent && expr.parent.kind === SyntaxKind.ExpressionStatement &&
!!getJSDocTypeTag(expr.parent);
}
@@ -2083,7 +2075,7 @@ namespace ts {
function getSourceOfDefaultedAssignment(node: Node): Node | undefined {
return isExpressionStatement(node) &&
isBinaryExpression(node.expression) &&
getSpecialPropertyAssignmentKind(node.expression) !== SpecialPropertyAssignmentKind.None &&
getAssignmentDeclarationKind(node.expression) !== AssignmentDeclarationKind.None &&
isBinaryExpression(node.expression.right) &&
node.expression.right.operatorToken.kind === SyntaxKind.BarBarToken
? node.expression.right.right
@@ -2403,7 +2395,7 @@ namespace ts {
else {
const binExp = parent.parent;
return isBinaryExpression(binExp) &&
getSpecialPropertyAssignmentKind(binExp) !== SpecialPropertyAssignmentKind.None &&
getAssignmentDeclarationKind(binExp) !== AssignmentDeclarationKind.None &&
(binExp.left.symbol || binExp.symbol) &&
getNameOfDeclaration(binExp) === name
? binExp
@@ -2472,7 +2464,7 @@ namespace ts {
node.kind === SyntaxKind.ImportSpecifier ||
node.kind === SyntaxKind.ExportSpecifier ||
node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(<ExportAssignment>node) ||
isBinaryExpression(node) && getSpecialPropertyAssignmentKind(node) === SpecialPropertyAssignmentKind.ModuleExports;
isBinaryExpression(node) && getAssignmentDeclarationKind(node) === AssignmentDeclarationKind.ModuleExports;
}
export function exportAssignmentIsAlias(node: ExportAssignment | BinaryExpression): boolean {
@@ -2481,7 +2473,7 @@ namespace ts {
}
export function getEffectiveBaseTypeNode(node: ClassLikeDeclaration | InterfaceDeclaration) {
if (isInJavaScriptFile(node)) {
if (isInJSFile(node)) {
// Prefer an @augments tag because it may have type parameters.
const tag = getJSDocAugmentsTag(node);
if (tag) {
@@ -3253,7 +3245,7 @@ namespace ts {
}
export interface EmitFileNames {
jsFilePath: string;
jsFilePath: string | undefined;
sourceMapFilePath: string | undefined;
declarationFilePath: string | undefined;
declarationMapPath: string | undefined;
@@ -3274,7 +3266,7 @@ namespace ts {
const isSourceFileFromExternalLibrary = (file: SourceFile) => host.isSourceFileFromExternalLibrary(file);
if (options.outFile || options.out) {
const moduleKind = getEmitModuleKind(options);
const moduleEmitEnabled = moduleKind === ModuleKind.AMD || moduleKind === ModuleKind.System;
const moduleEmitEnabled = options.emitDeclarationOnly || moduleKind === ModuleKind.AMD || moduleKind === ModuleKind.System;
// Can emit only sources that are not declaration file and are either non module code or module with --module or --target es6 specified
return filter(host.getSourceFiles(), sourceFile =>
(moduleEmitEnabled || !isExternalModule(sourceFile)) && sourceFileMayBeEmitted(sourceFile, options, isSourceFileFromExternalLibrary));
@@ -3287,7 +3279,7 @@ namespace ts {
/** Don't call this for `--outFile`, just for `--outDir` or plain emit. `--outFile` needs additional checks. */
export function sourceFileMayBeEmitted(sourceFile: SourceFile, options: CompilerOptions, isSourceFileFromExternalLibrary: (file: SourceFile) => boolean) {
return !(options.noEmitForJsFiles && isSourceFileJavaScript(sourceFile)) && !sourceFile.isDeclarationFile && !isSourceFileFromExternalLibrary(sourceFile);
return !(options.noEmitForJsFiles && isSourceFileJS(sourceFile)) && !sourceFile.isDeclarationFile && !isSourceFileFromExternalLibrary(sourceFile);
}
export function getSourceFilePathInNewDir(fileName: string, host: EmitHost, newDirPath: string): string {
@@ -3411,7 +3403,7 @@ namespace ts {
*/
export function getEffectiveTypeAnnotationNode(node: Node): TypeNode | undefined {
const type = (node as HasType).type;
if (type || !isInJavaScriptFile(node)) return type;
if (type || !isInJSFile(node)) return type;
return isJSDocPropertyLikeTag(node) ? node.typeExpression && node.typeExpression.type : getJSDocType(node);
}
@@ -3426,7 +3418,7 @@ namespace ts {
export function getEffectiveReturnTypeNode(node: SignatureDeclaration | JSDocSignature): TypeNode | undefined {
return isJSDocSignature(node) ?
node.type && node.type.typeExpression && node.type.typeExpression.type :
node.type || (isInJavaScriptFile(node) ? getJSDocReturnType(node) : undefined);
node.type || (isInJSFile(node) ? getJSDocReturnType(node) : undefined);
}
export function getJSDocTypeParameterDeclarations(node: DeclarationWithTypeParameters): ReadonlyArray<TypeParameterDeclaration> {
@@ -3743,11 +3735,20 @@ namespace ts {
/** Get `C` given `N` if `N` is in the position `class C extends N` where `N` is an ExpressionWithTypeArguments. */
export function tryGetClassExtendingExpressionWithTypeArguments(node: Node): ClassLikeDeclaration | undefined {
if (isExpressionWithTypeArguments(node) &&
node.parent.token === SyntaxKind.ExtendsKeyword &&
isClassLike(node.parent.parent)) {
return node.parent.parent;
}
const cls = tryGetClassImplementingOrExtendingExpressionWithTypeArguments(node);
return cls && !cls.isImplements ? cls.class : undefined;
}
export interface ClassImplementingOrExtendingExpressionWithTypeArguments {
readonly class: ClassLikeDeclaration;
readonly isImplements: boolean;
}
export function tryGetClassImplementingOrExtendingExpressionWithTypeArguments(node: Node): ClassImplementingOrExtendingExpressionWithTypeArguments | undefined {
return isExpressionWithTypeArguments(node)
&& isHeritageClause(node.parent)
&& isClassLike(node.parent.parent)
? { class: node.parent.parent, isImplements: node.parent.token === SyntaxKind.ImplementsKeyword }
: undefined;
}
export function isAssignmentExpression(node: Node, excludeCompoundAssignment: true): node is AssignmentExpression<EqualsToken>;
@@ -3774,15 +3775,6 @@ namespace ts {
return tryGetClassExtendingExpressionWithTypeArguments(node) !== undefined;
}
export function isExpressionWithTypeArgumentsInClassImplementsClause(node: Node): node is ExpressionWithTypeArguments {
return node.kind === SyntaxKind.ExpressionWithTypeArguments
&& isEntityNameExpression((node as ExpressionWithTypeArguments).expression)
&& node.parent
&& (<HeritageClause>node.parent).token === SyntaxKind.ImplementsKeyword
&& node.parent.parent
&& isClassLike(node.parent.parent);
}
export function isEntityNameExpression(node: Node): node is EntityNameExpression {
return node.kind === SyntaxKind.Identifier || isPropertyAccessEntityNameExpression(node);
}
@@ -3819,8 +3811,8 @@ namespace ts {
}
/** Return ".ts", ".d.ts", or ".tsx", if that is the extension. */
export function tryExtractTypeScriptExtension(fileName: string): string | undefined {
return find(supportedTypescriptExtensionsForExtractExtension, extension => fileExtensionIs(fileName, extension));
export function tryExtractTSExtension(fileName: string): string | undefined {
return find(supportedTSExtensionsForExtractExtension, extension => fileExtensionIs(fileName, extension));
}
/**
* Replace each instance of non-ascii characters by one, two, three, or four escape sequences
@@ -3974,6 +3966,27 @@ namespace ts {
return getStringFromExpandedCharCodes(expandedCharCodes);
}
export function readJson(path: string, host: { readFile(fileName: string): string | undefined }): object {
try {
const jsonText = host.readFile(path);
if (!jsonText) return {};
const result = parseConfigFileTextToJson(path, jsonText);
if (result.error) {
return {};
}
return result.config;
}
catch (e) {
// gracefully handle if readFile fails or returns not JSON
return {};
}
}
export function directoryProbablyExists(directoryName: string, host: { directoryExists?: (directoryName: string) => boolean }): boolean {
// if host does not support 'directoryExists' assume that directory will exist
return !host.directoryExists || host.directoryExists(directoryName);
}
const carriageReturnLineFeed = "\r\n";
const lineFeed = "\n";
export function getNewLineCharacter(options: CompilerOptions | PrinterOptions, getNewLine?: () => string): string {
@@ -4309,7 +4322,7 @@ namespace ts {
/**
* clears already present map by calling onDeleteExistingValue callback before deleting that key/value
*/
export function clearMap<T>(map: Map<T>, onDeleteValue: (valueInMap: T, key: string) => void) {
export function clearMap<T>(map: { forEach: Map<T>["forEach"]; clear: Map<T>["clear"]; }, onDeleteValue: (valueInMap: T, key: string) => void) {
// Remove all
map.forEach(onDeleteValue);
map.clear();
@@ -4966,11 +4979,11 @@ namespace ts {
}
case SyntaxKind.BinaryExpression: {
const expr = declaration as BinaryExpression;
switch (getSpecialPropertyAssignmentKind(expr)) {
case SpecialPropertyAssignmentKind.ExportsProperty:
case SpecialPropertyAssignmentKind.ThisProperty:
case SpecialPropertyAssignmentKind.Property:
case SpecialPropertyAssignmentKind.PrototypeProperty:
switch (getAssignmentDeclarationKind(expr)) {
case AssignmentDeclarationKind.ExportsProperty:
case AssignmentDeclarationKind.ThisProperty:
case AssignmentDeclarationKind.Property:
case AssignmentDeclarationKind.PrototypeProperty:
return (expr.left as PropertyAccessExpression).name;
default:
return undefined;
@@ -5187,7 +5200,7 @@ namespace ts {
if (node.typeParameters) {
return node.typeParameters;
}
if (isInJavaScriptFile(node)) {
if (isInJSFile(node)) {
const decls = getJSDocTypeParameterDeclarations(node);
if (decls.length) {
return decls;
@@ -6593,7 +6606,7 @@ namespace ts {
/* @internal */
export function isDeclaration(node: Node): node is NamedDeclaration {
if (node.kind === SyntaxKind.TypeParameter) {
return node.parent.kind !== SyntaxKind.JSDocTemplateTag || isInJavaScriptFile(node);
return node.parent.kind !== SyntaxKind.JSDocTemplateTag || isInJSFile(node);
}
return isDeclarationKind(node.kind);
@@ -7065,28 +7078,27 @@ namespace ts {
const moduleKind = getEmitModuleKind(compilerOptions);
return compilerOptions.allowSyntheticDefaultImports !== undefined
? compilerOptions.allowSyntheticDefaultImports
: compilerOptions.esModuleInterop
? moduleKind !== ModuleKind.None && moduleKind < ModuleKind.ES2015
: moduleKind === ModuleKind.System;
: compilerOptions.esModuleInterop ||
moduleKind === ModuleKind.System;
}
export function getEmitDeclarations(compilerOptions: CompilerOptions): boolean {
return !!(compilerOptions.declaration || compilerOptions.composite);
}
export type StrictOptionName = "noImplicitAny" | "noImplicitThis" | "strictNullChecks" | "strictFunctionTypes" | "strictPropertyInitialization" | "alwaysStrict";
export type StrictOptionName = "noImplicitAny" | "noImplicitThis" | "strictNullChecks" | "strictFunctionTypes" | "strictBindCallApply" | "strictPropertyInitialization" | "alwaysStrict";
export function getStrictOptionValue(compilerOptions: CompilerOptions, flag: StrictOptionName): boolean {
return compilerOptions[flag] === undefined ? !!compilerOptions.strict : !!compilerOptions[flag];
}
export function compilerOptionsAffectSemanticDiagnostics(newOptions: CompilerOptions, oldOptions: CompilerOptions) {
if (oldOptions === newOptions) {
return false;
}
export function compilerOptionsAffectSemanticDiagnostics(newOptions: CompilerOptions, oldOptions: CompilerOptions): boolean {
return oldOptions !== newOptions &&
semanticDiagnosticsOptionDeclarations.some(option => !isJsonEqual(getCompilerOptionValue(oldOptions, option), getCompilerOptionValue(newOptions, option)));
}
return optionDeclarations.some(option => (!!option.strictFlag && getStrictOptionValue(newOptions, option.name as StrictOptionName) !== getStrictOptionValue(oldOptions, option.name as StrictOptionName)) ||
(!!option.affectsSemanticDiagnostics && !newOptions[option.name] !== !oldOptions[option.name]));
export function getCompilerOptionValue(options: CompilerOptions, option: CommandLineOption): unknown {
return option.strictFlag ? getStrictOptionValue(options, option.name as StrictOptionName) : options[option.name];
}
export function hasZeroOrOneAsteriskCharacter(str: string): boolean {
@@ -7651,6 +7663,15 @@ namespace ts {
// It may be inefficient (we could just match (/[-[\]{}()*+?.,\\^$|#\s]/g), but this is future
// proof.
const reservedCharacterPattern = /[^\w\s\/]/g;
export function regExpEscape(text: string) {
return text.replace(reservedCharacterPattern, escapeRegExpCharacter);
}
function escapeRegExpCharacter(match: string) {
return "\\" + match;
}
const wildcardCharCodes = [CharacterCodes.asterisk, CharacterCodes.question];
export function hasExtension(fileName: string): boolean {
@@ -7717,7 +7738,7 @@ namespace ts {
return `^(${pattern})${terminator}`;
}
function getRegularExpressionsForWildcards(specs: ReadonlyArray<string> | undefined, basePath: string, usage: "files" | "directories" | "exclude"): string[] | undefined {
export function getRegularExpressionsForWildcards(specs: ReadonlyArray<string> | undefined, basePath: string, usage: "files" | "directories" | "exclude"): string[] | undefined {
if (specs === undefined || specs.length === 0) {
return undefined;
}
@@ -7981,48 +8002,58 @@ namespace ts {
/**
* List of supported extensions in order of file resolution precedence.
*/
export const supportedTypeScriptExtensions: ReadonlyArray<Extension> = [Extension.Ts, Extension.Tsx, Extension.Dts];
export const supportedTSExtensions: ReadonlyArray<Extension> = [Extension.Ts, Extension.Tsx, Extension.Dts];
export const supportedTSExtensionsWithJson: ReadonlyArray<Extension> = [Extension.Ts, Extension.Tsx, Extension.Dts, Extension.Json];
/** Must have ".d.ts" first because if ".ts" goes first, that will be detected as the extension instead of ".d.ts". */
export const supportedTypescriptExtensionsForExtractExtension: ReadonlyArray<Extension> = [Extension.Dts, Extension.Ts, Extension.Tsx];
export const supportedJavascriptExtensions: ReadonlyArray<Extension> = [Extension.Js, Extension.Jsx];
export const supportedJavaScriptAndJsonExtensions: ReadonlyArray<Extension> = [Extension.Js, Extension.Jsx, Extension.Json];
const allSupportedExtensions: ReadonlyArray<Extension> = [...supportedTypeScriptExtensions, ...supportedJavascriptExtensions];
export const supportedTSExtensionsForExtractExtension: ReadonlyArray<Extension> = [Extension.Dts, Extension.Ts, Extension.Tsx];
export const supportedJSExtensions: ReadonlyArray<Extension> = [Extension.Js, Extension.Jsx];
export const supportedJSAndJsonExtensions: ReadonlyArray<Extension> = [Extension.Js, Extension.Jsx, Extension.Json];
const allSupportedExtensions: ReadonlyArray<Extension> = [...supportedTSExtensions, ...supportedJSExtensions];
const allSupportedExtensionsWithJson: ReadonlyArray<Extension> = [...supportedTSExtensions, ...supportedJSExtensions, Extension.Json];
export function getSupportedExtensions(options?: CompilerOptions, extraFileExtensions?: ReadonlyArray<FileExtensionInfo>): ReadonlyArray<string> {
const needJsExtensions = options && options.allowJs;
if (!extraFileExtensions || extraFileExtensions.length === 0) {
return needJsExtensions ? allSupportedExtensions : supportedTypeScriptExtensions;
return needJsExtensions ? allSupportedExtensions : supportedTSExtensions;
}
const extensions = [
...needJsExtensions ? allSupportedExtensions : supportedTypeScriptExtensions,
...mapDefined(extraFileExtensions, x => x.scriptKind === ScriptKind.Deferred || needJsExtensions && isJavaScriptLike(x.scriptKind) ? x.extension : undefined)
...needJsExtensions ? allSupportedExtensions : supportedTSExtensions,
...mapDefined(extraFileExtensions, x => x.scriptKind === ScriptKind.Deferred || needJsExtensions && isJSLike(x.scriptKind) ? x.extension : undefined)
];
return deduplicate<string>(extensions, equateStringsCaseSensitive, compareStringsCaseSensitive);
}
function isJavaScriptLike(scriptKind: ScriptKind | undefined): boolean {
export function getSuppoertedExtensionsWithJsonIfResolveJsonModule(options: CompilerOptions | undefined, supportedExtensions: ReadonlyArray<string>): ReadonlyArray<string> {
if (!options || !options.resolveJsonModule) { return supportedExtensions; }
if (supportedExtensions === allSupportedExtensions) { return allSupportedExtensionsWithJson; }
if (supportedExtensions === supportedTSExtensions) { return supportedTSExtensionsWithJson; }
return [...supportedExtensions, Extension.Json];
}
function isJSLike(scriptKind: ScriptKind | undefined): boolean {
return scriptKind === ScriptKind.JS || scriptKind === ScriptKind.JSX;
}
export function hasJavaScriptFileExtension(fileName: string): boolean {
return some(supportedJavascriptExtensions, extension => fileExtensionIs(fileName, extension));
export function hasJSFileExtension(fileName: string): boolean {
return some(supportedJSExtensions, extension => fileExtensionIs(fileName, extension));
}
export function hasJavaScriptOrJsonFileExtension(fileName: string): boolean {
return supportedJavaScriptAndJsonExtensions.some(ext => fileExtensionIs(fileName, ext));
export function hasJSOrJsonFileExtension(fileName: string): boolean {
return supportedJSAndJsonExtensions.some(ext => fileExtensionIs(fileName, ext));
}
export function hasTypeScriptFileExtension(fileName: string): boolean {
return some(supportedTypeScriptExtensions, extension => fileExtensionIs(fileName, extension));
export function hasTSFileExtension(fileName: string): boolean {
return some(supportedTSExtensions, extension => fileExtensionIs(fileName, extension));
}
export function isSupportedSourceFileName(fileName: string, compilerOptions?: CompilerOptions, extraFileExtensions?: ReadonlyArray<FileExtensionInfo>) {
if (!fileName) { return false; }
for (const extension of getSupportedExtensions(compilerOptions, extraFileExtensions)) {
const supportedExtensions = getSupportedExtensions(compilerOptions, extraFileExtensions);
for (const extension of getSuppoertedExtensionsWithJsonIfResolveJsonModule(compilerOptions, supportedExtensions)) {
if (fileExtensionIs(fileName, extension)) {
return true;
}
@@ -8152,12 +8183,12 @@ namespace ts {
}
/** True if an extension is one of the supported TypeScript extensions. */
export function extensionIsTypeScript(ext: Extension): boolean {
export function extensionIsTS(ext: Extension): boolean {
return ext === Extension.Ts || ext === Extension.Tsx || ext === Extension.Dts;
}
export function resolutionExtensionIsTypeScriptOrJson(ext: Extension) {
return extensionIsTypeScript(ext) || ext === Extension.Json;
export function resolutionExtensionIsTSOrJson(ext: Extension) {
return extensionIsTS(ext) || ext === Extension.Json;
}
/**
@@ -8345,4 +8376,20 @@ namespace ts {
// '/// <reference no-default-lib="true"/>' directive.
return options.skipLibCheck && sourceFile.isDeclarationFile || options.skipDefaultLibCheck && sourceFile.hasNoDefaultLib;
}
export function isJsonEqual(a: unknown, b: unknown): boolean {
return a === b || typeof a === "object" && a !== null && typeof b === "object" && b !== null && equalOwnProperties(a as MapLike<unknown>, b as MapLike<unknown>, isJsonEqual);
}
export function getOrUpdate<T>(map: Map<T>, key: string, getDefault: () => T): T {
const got = map.get(key);
if (got === undefined) {
const value = getDefault();
map.set(key, value);
return value;
}
else {
return got;
}
}
}
+24 -21
View File
@@ -101,7 +101,7 @@ namespace ts {
getGlobalDiagnostics(): ReadonlyArray<Diagnostic>;
getSemanticDiagnostics(): ReadonlyArray<Diagnostic>;
getConfigFileParsingDiagnostics(): ReadonlyArray<Diagnostic>;
emit(): EmitResult;
emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback): EmitResult;
}
export type ReportEmitErrorSummary = (errorCount: number) => void;
@@ -109,7 +109,7 @@ namespace ts {
/**
* Helper that emit files, report diagnostics and lists emitted and/or source files depending on compiler options
*/
export function emitFilesAndReportErrors(program: ProgramToEmitFilesAndReportErrors, reportDiagnostic: DiagnosticReporter, writeFileName?: (s: string) => void, reportSummary?: ReportEmitErrorSummary) {
export function emitFilesAndReportErrors(program: ProgramToEmitFilesAndReportErrors, reportDiagnostic: DiagnosticReporter, writeFileName?: (s: string) => void, reportSummary?: ReportEmitErrorSummary, writeFile?: WriteFileCallback) {
// First get and report any syntactic errors.
const diagnostics = program.getConfigFileParsingDiagnostics().slice();
const configFileParsingDiagnosticsLength = diagnostics.length;
@@ -128,7 +128,7 @@ namespace ts {
}
// Emit and report any errors we ran into.
const { emittedFiles, emitSkipped, diagnostics: emitDiagnostics } = program.emit();
const { emittedFiles, emitSkipped, diagnostics: emitDiagnostics } = program.emit(/*targetSourceFile*/ undefined, writeFile);
addRange(diagnostics, emitDiagnostics);
if (reportSemanticDiagnostics) {
@@ -263,10 +263,11 @@ namespace ts {
/**
* Creates the watch compiler host from system for compiling root files and options in watch mode
*/
export function createWatchCompilerHostOfFilesAndCompilerOptions<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(rootFiles: string[], options: CompilerOptions, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): WatchCompilerHostOfFilesAndCompilerOptions<T> {
export function createWatchCompilerHostOfFilesAndCompilerOptions<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(rootFiles: string[], options: CompilerOptions, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, projectReferences?: ReadonlyArray<ProjectReference>): WatchCompilerHostOfFilesAndCompilerOptions<T> {
const host = createWatchCompilerHost(system, createProgram, reportDiagnostic || createDiagnosticReporter(system), reportWatchStatus) as WatchCompilerHostOfFilesAndCompilerOptions<T>;
host.rootFiles = rootFiles;
host.options = options;
host.projectReferences = projectReferences;
return host;
}
}
@@ -274,7 +275,7 @@ namespace ts {
namespace ts {
export type WatchStatusReporter = (diagnostic: Diagnostic, newLine: string, options: CompilerOptions) => void;
/** Create the program with rootNames and options, if they are undefined, oldProgram and new configFile diagnostics create new program */
export type CreateProgram<T extends BuilderProgram> = (rootNames: ReadonlyArray<string> | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: T, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>) => T;
export type CreateProgram<T extends BuilderProgram> = (rootNames: ReadonlyArray<string> | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: T, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>, projectReferences?: ReadonlyArray<ProjectReference> | undefined) => T;
/** Host that has watch functionality used in --watch mode */
export interface WatchHost {
/** If provided, called with Diagnostic message that informs about change in watch status */
@@ -360,6 +361,9 @@ namespace ts {
/** Compiler options */
options: CompilerOptions;
/** Project References */
projectReferences?: ReadonlyArray<ProjectReference>;
}
/**
@@ -413,11 +417,11 @@ namespace ts {
/**
* Create the watch compiler host for either configFile or fileNames and its options
*/
export function createWatchCompilerHost<T extends BuilderProgram>(rootFiles: string[], options: CompilerOptions, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): WatchCompilerHostOfFilesAndCompilerOptions<T>;
export function createWatchCompilerHost<T extends BuilderProgram>(configFileName: string, optionsToExtend: CompilerOptions | undefined, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): WatchCompilerHostOfConfigFile<T>;
export function createWatchCompilerHost<T extends BuilderProgram>(rootFilesOrConfigFileName: string | string[], options: CompilerOptions | undefined, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): WatchCompilerHostOfFilesAndCompilerOptions<T> | WatchCompilerHostOfConfigFile<T> {
export function createWatchCompilerHost<T extends BuilderProgram>(rootFiles: string[], options: CompilerOptions, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, projectReferences?: ReadonlyArray<ProjectReference>): WatchCompilerHostOfFilesAndCompilerOptions<T>;
export function createWatchCompilerHost<T extends BuilderProgram>(rootFilesOrConfigFileName: string | string[], options: CompilerOptions | undefined, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, projectReferences?: ReadonlyArray<ProjectReference>): WatchCompilerHostOfFilesAndCompilerOptions<T> | WatchCompilerHostOfConfigFile<T> {
if (isArray(rootFilesOrConfigFileName)) {
return createWatchCompilerHostOfFilesAndCompilerOptions(rootFilesOrConfigFileName, options!, system, createProgram, reportDiagnostic, reportWatchStatus); // TODO: GH#18217
return createWatchCompilerHostOfFilesAndCompilerOptions(rootFilesOrConfigFileName, options!, system, createProgram, reportDiagnostic, reportWatchStatus, projectReferences); // TODO: GH#18217
}
else {
return createWatchCompilerHostOfConfigFile(rootFilesOrConfigFileName, options, system, createProgram, reportDiagnostic, reportWatchStatus);
@@ -463,9 +467,10 @@ namespace ts {
const getCurrentDirectory = () => currentDirectory;
const readFile: (path: string, encoding?: string) => string | undefined = (path, encoding) => host.readFile(path, encoding);
const { configFileName, optionsToExtend: optionsToExtendForConfigFile = {}, createProgram } = host;
let { rootFiles: rootFileNames, options: compilerOptions } = host;
let { rootFiles: rootFileNames, options: compilerOptions, projectReferences } = host;
let configFileSpecs: ConfigFileSpecs;
let configFileParsingDiagnostics: ReadonlyArray<Diagnostic> | undefined;
let configFileParsingDiagnostics: Diagnostic[] | undefined;
let canConfigFileJsonReportNoInputFiles = false;
let hasChangedConfigFileParsingErrors = false;
const cachedDirectoryStructureHost = configFileName === undefined ? undefined : createCachedDirectoryStructureHost(host, currentDirectory, useCaseSensitiveFileNames);
@@ -542,7 +547,8 @@ namespace ts {
},
maxNumberOfFilesToIterateForInvalidation: host.maxNumberOfFilesToIterateForInvalidation,
getCurrentProgram,
writeLog
writeLog,
readDirectory: (path, extensions, exclude, include, depth?) => directoryStructureHost.readDirectory!(path, extensions, exclude, include, depth),
};
// Cache for the module resolution
const resolutionCache = createResolutionCache(compilerHost, configFileName ?
@@ -589,9 +595,9 @@ namespace ts {
// All resolutions are invalid if user provided resolutions
const hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution(userProvidedResolution);
if (isProgramUptoDate(getCurrentProgram(), rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames)) {
if (isProgramUptoDate(getCurrentProgram(), rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames, projectReferences)) {
if (hasChangedConfigFileParsingErrors) {
builderProgram = createProgram(/*rootNames*/ undefined, /*options*/ undefined, compilerHost, builderProgram, configFileParsingDiagnostics);
builderProgram = createProgram(/*rootNames*/ undefined, /*options*/ undefined, compilerHost, builderProgram, configFileParsingDiagnostics, projectReferences);
hasChangedConfigFileParsingErrors = false;
}
}
@@ -620,7 +626,7 @@ namespace ts {
resolutionCache.startCachingPerDirectoryResolution();
compilerHost.hasInvalidatedResolution = hasInvalidatedResolution;
compilerHost.hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames;
builderProgram = createProgram(rootFileNames, compilerOptions, compilerHost, builderProgram, configFileParsingDiagnostics);
builderProgram = createProgram(rootFileNames, compilerOptions, compilerHost, builderProgram, configFileParsingDiagnostics, projectReferences);
resolutionCache.finishCachingPerDirectoryResolution();
// Update watches
@@ -824,12 +830,7 @@ namespace ts {
function reloadFileNamesFromConfigFile() {
writeLog("Reloading new file names and options");
const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, parseConfigFileHost);
if (result.fileNames.length) {
configFileParsingDiagnostics = filter(configFileParsingDiagnostics, error => !isErrorNoInputFiles(error));
hasChangedConfigFileParsingErrors = true;
}
else if (!configFileSpecs.filesSpecs && !some(configFileParsingDiagnostics, isErrorNoInputFiles)) {
configFileParsingDiagnostics = configFileParsingDiagnostics!.concat(getErrorForNoInputFiles(configFileSpecs, configFileName));
if (updateErrorForNoInputFiles(result, configFileName, configFileSpecs, configFileParsingDiagnostics!, canConfigFileJsonReportNoInputFiles)) {
hasChangedConfigFileParsingErrors = true;
}
rootFileNames = result.fileNames;
@@ -861,7 +862,9 @@ namespace ts {
rootFileNames = configFileParseResult.fileNames;
compilerOptions = configFileParseResult.options;
configFileSpecs = configFileParseResult.configFileSpecs!; // TODO: GH#18217
configFileParsingDiagnostics = getConfigFileParsingDiagnostics(configFileParseResult);
projectReferences = configFileParseResult.projectReferences;
configFileParsingDiagnostics = getConfigFileParsingDiagnostics(configFileParseResult).slice();
canConfigFileJsonReportNoInputFiles = canJsonReportNoInutFiles(configFileParseResult.raw);
hasChangedConfigFileParsingErrors = true;
}
+35 -24
View File
@@ -3,12 +3,15 @@ namespace ts.server {
writeMessage(message: string): void;
}
interface RenameEntry extends RenameInfo {
fileName: string;
position: number;
locations: RenameLocation[];
findInStrings: boolean;
findInComments: boolean;
interface RenameEntry {
readonly renameInfo: RenameInfo;
readonly inputs: {
readonly fileName: string;
readonly position: number;
readonly findInStrings: boolean;
readonly findInComments: boolean;
};
readonly locations: RenameLocation[];
}
/* @internal */
@@ -390,33 +393,41 @@ namespace ts.server {
const locations: RenameLocation[] = [];
for (const entry of body.locs) {
const fileName = entry.file;
for (const loc of entry.locs) {
locations.push({ textSpan: this.decodeSpan(loc, fileName), fileName });
for (const { start, end, ...prefixSuffixText } of entry.locs) {
locations.push({ textSpan: this.decodeSpan({ start, end }, fileName), fileName, ...prefixSuffixText });
}
}
return this.lastRenameEntry = {
canRename: body.info.canRename,
displayName: body.info.displayName,
fullDisplayName: body.info.fullDisplayName,
kind: body.info.kind,
kindModifiers: body.info.kindModifiers,
localizedErrorMessage: body.info.localizedErrorMessage,
triggerSpan: createTextSpanFromBounds(position, position),
fileName,
position,
findInStrings: !!findInStrings,
findInComments: !!findInComments,
const renameInfo = body.info.canRename
? identity<RenameInfoSuccess>({
canRename: body.info.canRename,
fileToRename: body.info.fileToRename,
displayName: body.info.displayName,
fullDisplayName: body.info.fullDisplayName,
kind: body.info.kind,
kindModifiers: body.info.kindModifiers,
triggerSpan: createTextSpanFromBounds(position, position),
})
: identity<RenameInfoFailure>({ canRename: false, localizedErrorMessage: body.info.localizedErrorMessage });
this.lastRenameEntry = {
renameInfo,
inputs: {
fileName,
position,
findInStrings: !!findInStrings,
findInComments: !!findInComments,
},
locations,
};
return renameInfo;
}
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[] {
if (!this.lastRenameEntry ||
this.lastRenameEntry.fileName !== fileName ||
this.lastRenameEntry.position !== position ||
this.lastRenameEntry.findInStrings !== findInStrings ||
this.lastRenameEntry.findInComments !== findInComments) {
this.lastRenameEntry.inputs.fileName !== fileName ||
this.lastRenameEntry.inputs.position !== position ||
this.lastRenameEntry.inputs.findInStrings !== findInStrings ||
this.lastRenameEntry.inputs.findInComments !== findInComments) {
this.getRenameInfo(fileName, position, findInStrings, findInComments);
}
+79 -33
View File
@@ -420,12 +420,12 @@ namespace FourSlash {
}
}
public goToEachRange(action: () => void) {
public goToEachRange(action: (range: Range) => void) {
const ranges = this.getRanges();
assert(ranges.length);
for (const range of ranges) {
this.selectRange(range);
action();
action(range);
}
}
@@ -593,7 +593,7 @@ namespace FourSlash {
public verifyNoErrors() {
ts.forEachKey(this.inputFiles, fileName => {
if (!ts.isAnySupportedFileExtension(fileName)
|| !this.getProgram().getCompilerOptions().allowJs && !ts.extensionIsTypeScript(ts.extensionFromPath(fileName))) return;
|| !this.getProgram().getCompilerOptions().allowJs && !ts.extensionIsTS(ts.extensionFromPath(fileName))) return;
const errors = this.getDiagnostics(fileName).filter(e => e.category !== ts.DiagnosticCategory.Suggestion);
if (errors.length) {
this.printErrorLog(/*expectErrors*/ false, errors);
@@ -908,8 +908,8 @@ namespace FourSlash {
}
private verifyCompletionEntry(actual: ts.CompletionEntry, expected: FourSlashInterface.ExpectedCompletionEntry) {
const { insertText, replacementSpan, hasAction, isRecommended, kind, text, documentation, source, sourceDisplay } = typeof expected === "string"
? { insertText: undefined, replacementSpan: undefined, hasAction: undefined, isRecommended: undefined, kind: undefined, text: undefined, documentation: undefined, source: undefined, sourceDisplay: undefined }
const { insertText, replacementSpan, hasAction, isRecommended, kind, text, documentation, tags, source, sourceDisplay } = typeof expected === "string"
? { insertText: undefined, replacementSpan: undefined, hasAction: undefined, isRecommended: undefined, kind: undefined, text: undefined, documentation: undefined, tags: undefined, source: undefined, sourceDisplay: undefined }
: expected;
if (actual.insertText !== insertText) {
@@ -929,7 +929,7 @@ namespace FourSlash {
assert.equal(actual.isRecommended, isRecommended);
assert.equal(actual.source, source);
if (text) {
if (text !== undefined) {
const actualDetails = this.getCompletionEntryDetails(actual.name, actual.source)!;
assert.equal(ts.displayPartsToString(actualDetails.displayParts), text);
assert.equal(ts.displayPartsToString(actualDetails.documentation), documentation || "");
@@ -937,9 +937,10 @@ namespace FourSlash {
// assert.equal(actualDetails.kind, actual.kind);
assert.equal(actualDetails.kindModifiers, actual.kindModifiers);
assert.equal(actualDetails.source && ts.displayPartsToString(actualDetails.source), sourceDisplay);
assert.deepEqual(actualDetails.tags, tags);
}
else {
assert(documentation === undefined && sourceDisplay === undefined, "If specifying completion details, should specify 'text'");
assert(documentation === undefined && tags === undefined && sourceDisplay === undefined, "If specifying completion details, should specify 'text'");
}
}
@@ -1363,7 +1364,7 @@ Actual: ${stringify(fullActual)}`);
public verifyQuickInfoDisplayParts(kind: string, kindModifiers: string, textSpan: TextSpan,
displayParts: ts.SymbolDisplayPart[],
documentation: ts.SymbolDisplayPart[],
tags: ts.JSDocTagInfo[]
tags: ts.JSDocTagInfo[] | undefined
) {
const actualQuickInfo = this.languageService.getQuickInfoAtPosition(this.activeFile.fileName, this.currentCaretPosition)!;
@@ -1372,11 +1373,16 @@ Actual: ${stringify(fullActual)}`);
assert.equal(JSON.stringify(actualQuickInfo.textSpan), JSON.stringify(textSpan), this.messageAtLastKnownMarker("QuickInfo textSpan"));
assert.equal(TestState.getDisplayPartsJson(actualQuickInfo.displayParts), TestState.getDisplayPartsJson(displayParts), this.messageAtLastKnownMarker("QuickInfo displayParts"));
assert.equal(TestState.getDisplayPartsJson(actualQuickInfo.documentation), TestState.getDisplayPartsJson(documentation), this.messageAtLastKnownMarker("QuickInfo documentation"));
assert.equal(actualQuickInfo.tags!.length, tags.length, this.messageAtLastKnownMarker("QuickInfo tags"));
ts.zipWith(tags, actualQuickInfo.tags!, (expectedTag, actualTag) => {
assert.equal(expectedTag.name, actualTag.name);
assert.equal(expectedTag.text, actualTag.text, this.messageAtLastKnownMarker("QuickInfo tag " + actualTag.name));
});
if (!actualQuickInfo.tags || !tags) {
assert.equal(actualQuickInfo.tags, tags, this.messageAtLastKnownMarker("QuickInfo tags"));
}
else {
assert.equal(actualQuickInfo.tags.length, tags.length, this.messageAtLastKnownMarker("QuickInfo tags"));
ts.zipWith(tags, actualQuickInfo.tags, (expectedTag, actualTag) => {
assert.equal(expectedTag.name, actualTag.name);
assert.equal(expectedTag.text, actualTag.text, this.messageAtLastKnownMarker("QuickInfo tag " + actualTag.name));
});
}
}
public verifyRangesAreRenameLocations(options?: Range[] | { findInStrings?: boolean, findInComments?: boolean, ranges?: Range[] }) {
@@ -1389,7 +1395,7 @@ Actual: ${stringify(fullActual)}`);
}
}
public verifyRenameLocations(startRanges: ArrayOrSingle<Range>, options: ReadonlyArray<Range> | { findInStrings?: boolean, findInComments?: boolean, ranges: ReadonlyArray<Range> }) {
public verifyRenameLocations(startRanges: ArrayOrSingle<Range>, options: FourSlashInterface.RenameLocationsOptions) {
const { findInStrings = false, findInComments = false, ranges = this.getRanges() } = ts.isArray(options) ? { findInStrings: false, findInComments: false, ranges: options } : options;
for (const startRange of toArray(startRanges)) {
@@ -1406,7 +1412,10 @@ Actual: ${stringify(fullActual)}`);
const sort = (locations: ReadonlyArray<ts.RenameLocation> | undefined) =>
locations && ts.sort(locations, (r1, r2) => ts.compareStringsCaseSensitive(r1.fileName, r2.fileName) || r1.textSpan.start - r2.textSpan.start);
assert.deepEqual(sort(references), sort(ranges.map((r): ts.RenameLocation => ({ fileName: r.fileName, textSpan: ts.createTextSpanFromRange(r) }))));
assert.deepEqual(sort(references), sort(ranges.map((rangeOrOptions): ts.RenameLocation => {
const { range, ...prefixSuffixText } = "range" in rangeOrOptions ? rangeOrOptions : { range: rangeOrOptions };
return { fileName: range.fileName, textSpan: ts.createTextSpanFromRange(range), ...prefixSuffixText };
})));
}
}
@@ -1525,22 +1534,25 @@ Actual: ${stringify(fullActual)}`);
}
}
public verifyRenameInfoSucceeded(displayName?: string, fullDisplayName?: string, kind?: string, kindModifiers?: string) {
public verifyRenameInfoSucceeded(displayName: string | undefined, fullDisplayName: string | undefined, kind: string | undefined, kindModifiers: string | undefined, fileToRename: string | undefined, expectedRange: Range | undefined): void {
const renameInfo = this.languageService.getRenameInfo(this.activeFile.fileName, this.currentCaretPosition);
if (!renameInfo.canRename) {
this.raiseError("Rename did not succeed");
throw this.raiseError("Rename did not succeed");
}
this.validate("displayName", displayName, renameInfo.displayName);
this.validate("fullDisplayName", fullDisplayName, renameInfo.fullDisplayName);
this.validate("kind", kind, renameInfo.kind);
this.validate("kindModifiers", kindModifiers, renameInfo.kindModifiers);
this.validate("fileToRename", fileToRename, renameInfo.fileToRename);
if (this.getRanges().length !== 1) {
this.raiseError("Expected a single range to be selected in the test file.");
if (!expectedRange) {
if (this.getRanges().length !== 1) {
this.raiseError("Expected a single range to be selected in the test file.");
}
expectedRange = this.getRanges()[0];
}
const expectedRange = this.getRanges()[0];
if (renameInfo.triggerSpan.start !== expectedRange.pos ||
ts.textSpanEnd(renameInfo.triggerSpan) !== expectedRange.end) {
this.raiseError("Expected triggerSpan [" + expectedRange.pos + "," + expectedRange.end + "). Got [" +
@@ -1551,9 +1563,8 @@ Actual: ${stringify(fullActual)}`);
public verifyRenameInfoFailed(message?: string) {
const renameInfo = this.languageService.getRenameInfo(this.activeFile.fileName, this.currentCaretPosition);
if (renameInfo.canRename) {
this.raiseError("Rename was expected to fail");
throw this.raiseError("Rename was expected to fail");
}
this.validate("error", message, renameInfo.localizedErrorMessage);
}
@@ -2493,9 +2504,7 @@ Actual: ${stringify(fullActual)}`);
const { changes, commands } = this.languageService.getCombinedCodeFix({ type: "file", fileName: this.activeFile.fileName }, fixId, this.formatCodeSettings, ts.emptyOptions);
assert.deepEqual<ReadonlyArray<{}> | undefined>(commands, expectedCommands);
assert(changes.every(c => c.fileName === this.activeFile.fileName), "TODO: support testing codefixes that touch multiple files");
this.applyChanges(changes);
this.verifyCurrentFileContent(newFileContent);
this.verifyNewContent({ newFileContent }, changes);
}
/**
@@ -2507,10 +2516,10 @@ Actual: ${stringify(fullActual)}`);
* @param expectedContents The contents of the file after the fixes are applied.
* @param fileName The file to check. If not supplied, the current open file is used.
*/
public verifyFileAfterCodeFix(expectedContents: string, fileName?: string) {
public verifyFileAfterCodeFix(expectedContents: string, fileName?: string, index?: number) {
fileName = fileName ? fileName : this.activeFile.fileName;
this.applyCodeActions(this.getCodeFixes(fileName));
this.applyCodeActions(this.getCodeFixes(fileName), index);
const actualContents: string = this.getFileContent(fileName);
if (this.removeWhitespace(actualContents) !== this.removeWhitespace(expectedContents)) {
@@ -3380,6 +3389,19 @@ Actual: ${stringify(fullActual)}`);
private getApplicableRefactorsWorker(positionOrRange: number | ts.TextRange, fileName: string, preferences = ts.emptyOptions): ReadonlyArray<ts.ApplicableRefactorInfo> {
return this.languageService.getApplicableRefactors(fileName, positionOrRange, preferences) || ts.emptyArray;
}
public generateTypes(examples: ReadonlyArray<FourSlashInterface.GenerateTypesOptions>): void {
for (const { name = "example", value, output, outputBaseline } of examples) {
const actual = ts.generateTypesForModule(name, value, this.formatCodeSettings);
if (outputBaseline) {
if (actual === undefined) throw ts.Debug.fail();
Harness.Baseline.runBaseline(ts.combinePaths("generateTypes", outputBaseline + ts.Extension.Dts), actual);
}
else {
assert.equal(actual, output, `generateTypes output for ${name} does not match`);
}
}
}
}
function updateTextRangeForTextChanges({ pos, end }: ts.TextRange, textChanges: ReadonlyArray<ts.TextChange>): ts.TextRange {
@@ -3433,7 +3455,7 @@ Actual: ${stringify(fullActual)}`);
// Parse out the files and their metadata
const testData = parseTestData(absoluteBasePath, content, absoluteFileName);
const state = new TestState(absoluteBasePath, testType, testData);
const output = ts.transpileModule(content, { reportDiagnostics: true });
const output = ts.transpileModule(content, { reportDiagnostics: true, compilerOptions: { target: ts.ScriptTarget.ES2015 } });
if (output.diagnostics!.length > 0) {
throw new Error(`Syntax error in ${absoluteBasePath}: ${output.diagnostics![0].messageText}`);
}
@@ -3977,7 +3999,7 @@ namespace FourSlashInterface {
this.state.goToRangeStart(range);
}
public eachRange(action: () => void) {
public eachRange(action: (range: FourSlash.Range) => void) {
this.state.goToEachRange(action);
}
@@ -4366,6 +4388,10 @@ namespace FourSlashInterface {
this.state.verifyRangeAfterCodeFix(expectedText, includeWhiteSpace, errorCode, index);
}
public fileAfterCodeFix(expectedContents: string, fileName?: string, index?: number) {
this.state.verifyFileAfterCodeFix(expectedContents, fileName, index);
}
public codeFixAll(options: VerifyCodeFixAllOptions): void {
this.state.verifyCodeFixAll(options);
}
@@ -4456,15 +4482,15 @@ namespace FourSlashInterface {
this.state.verifySemanticClassifications(classifications);
}
public renameInfoSucceeded(displayName?: string, fullDisplayName?: string, kind?: string, kindModifiers?: string) {
this.state.verifyRenameInfoSucceeded(displayName, fullDisplayName, kind, kindModifiers);
public renameInfoSucceeded(displayName?: string, fullDisplayName?: string, kind?: string, kindModifiers?: string, fileToRename?: string, expectedRange?: FourSlash.Range) {
this.state.verifyRenameInfoSucceeded(displayName, fullDisplayName, kind, kindModifiers, fileToRename, expectedRange);
}
public renameInfoFailed(message?: string) {
this.state.verifyRenameInfoFailed(message);
}
public renameLocations(startRanges: ArrayOrSingle<FourSlash.Range>, options: FourSlash.Range[] | { findInStrings?: boolean, findInComments?: boolean, ranges: FourSlash.Range[] }) {
public renameLocations(startRanges: ArrayOrSingle<FourSlash.Range>, options: RenameLocationsOptions) {
this.state.verifyRenameLocations(startRanges, options);
}
@@ -4503,6 +4529,18 @@ namespace FourSlashInterface {
public noMoveToNewFile(): void {
this.state.noMoveToNewFile();
}
public generateTypes(...options: GenerateTypesOptions[]): void {
this.state.generateTypes(options);
}
}
export interface GenerateTypesOptions {
readonly name?: string;
readonly value: unknown;
// Exactly one of these should be set:
readonly output?: string;
readonly outputBaseline?: string;
}
export class Edit {
@@ -4799,6 +4837,7 @@ namespace FourSlashInterface {
readonly text: string;
readonly documentation: string;
readonly sourceDisplay?: string;
readonly tags?: ReadonlyArray<ts.JSDocTagInfo>;
};
export interface CompletionsAtOptions extends Partial<ts.UserPreferences> {
triggerCharacter?: ts.CompletionsTriggerCharacter;
@@ -4891,7 +4930,7 @@ namespace FourSlashInterface {
export interface VerifyCodeFixAllOptions {
fixId: string;
fixAllDescription: string;
newFileContent: string;
newFileContent: NewFileContent;
commands: ReadonlyArray<{}>;
}
@@ -4926,4 +4965,11 @@ namespace FourSlashInterface {
readonly newFileContents: { readonly [fileName: string]: string };
readonly preferences?: ts.UserPreferences;
}
export type RenameLocationsOptions = ReadonlyArray<RenameLocationOptions> | {
readonly findInStrings?: boolean;
readonly findInComments?: boolean;
readonly ranges: ReadonlyArray<RenameLocationOptions>;
};
export type RenameLocationOptions = FourSlash.Range | { readonly range: FourSlash.Range, readonly prefixText?: string, readonly suffixText?: string };
}
+1 -1
View File
@@ -1159,7 +1159,7 @@ namespace Harness {
}
// If not a primitive, the possible types are specified in what is effectively a map of options.
case "list":
return ts.parseListTypeOption(<ts.CommandLineOptionOfListType>option, value, errors);
return ts.parseListTypeOption(option, value, errors);
default:
return ts.parseCustomTypeOption(<ts.CommandLineOptionOfCustomType>option, value, errors);
}
+1 -1
View File
@@ -268,7 +268,7 @@ namespace Harness.LanguageService {
getHost(): LanguageServiceAdapterHost { return this.host; }
getLanguageService(): ts.LanguageService { return ts.createLanguageService(this.host); }
getClassifier(): ts.Classifier { return ts.createClassifier(); }
getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo { return ts.preProcessFile(fileContents, /* readImportFiles */ true, ts.hasJavaScriptFileExtension(fileName)); }
getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo { return ts.preProcessFile(fileContents, /* readImportFiles */ true, ts.hasJSFileExtension(fileName)); }
}
/// Shim adapter
+27
View File
@@ -7,6 +7,21 @@ namespace utils {
return text !== undefined ? text.replace(testPathPrefixRegExp, (_, scheme) => scheme || (retainTrailingDirectorySeparator ? "/" : "")) : undefined!; // TODO: GH#18217
}
function createDiagnosticMessageReplacer<R extends (messageArgs: string[], ...args: string[]) => string[]>(diagnosticMessage: ts.DiagnosticMessage, replacer: R) {
const messageParts = diagnosticMessage.message.split(/{\d+}/g);
const regExp = new RegExp(`^(?:${messageParts.map(ts.regExpEscape).join("(.*?)")})$`);
type Args<R> = R extends (messageArgs: string[], ...args: infer A) => string[] ? A : [];
return (text: string, ...args: Args<R>) => text.replace(regExp, (_, ...fixedArgs) => ts.formatStringFromArgs(diagnosticMessage.message, replacer(fixedArgs, ...args)));
}
const replaceTypesVersionsMessage = createDiagnosticMessageReplacer(
ts.Diagnostics.package_json_has_a_typesVersions_entry_0_that_matches_compiler_version_1_looking_for_a_pattern_to_match_module_name_2,
([entry, , moduleName], compilerVersion) => [entry, compilerVersion, moduleName]);
export function sanitizeTraceResolutionLogEntry(text: string) {
return text && removeTestPathPrefixes(replaceTypesVersionsMessage(text, "3.1.0-dev"));
}
/**
* Removes leading indentation from a template literal string.
*/
@@ -82,4 +97,16 @@ namespace utils {
export function addUTF8ByteOrderMark(text: string) {
return getByteOrderMarkLength(text) === 0 ? "\u00EF\u00BB\u00BF" + text : text;
}
export function theory<T extends any[]>(name: string, cb: (...args: T) => void, data: T[]) {
for (const entry of data) {
it(`${name}(${entry.map(formatTheoryDatum).join(", ")})`, () => cb(...entry));
}
}
function formatTheoryDatum(value: any) {
return typeof value === "function" ? value.name || "<anonymous function>" :
value === undefined ? "undefined" :
JSON.stringify(value);
}
}
+11 -3
View File
@@ -4,6 +4,8 @@ namespace ts.TestFSWithWatch {
content: `/// <reference no-default-lib="true"/>
interface Boolean {}
interface Function {}
interface CallableFunction {}
interface NewableFunction {}
interface IArguments {}
interface Number { toExponential: any; }
interface Object {}
@@ -187,9 +189,10 @@ interface Array<T> {}`
}
export function checkArray(caption: string, actual: ReadonlyArray<string>, expected: ReadonlyArray<string>) {
checkMapKeys(caption, arrayToMap(actual, identity), expected);
assert.equal(actual.length, expected.length, `${caption}: incorrect actual number of files, expected:\r\n${expected.join("\r\n")}\r\ngot: ${actual.join("\r\n")}`);
for (const f of expected) {
assert.equal(true, contains(actual, f), `${caption}: expected to find ${f} in ${actual}`);
assert.isTrue(contains(actual, f), `${caption}: expected to find ${f} in ${actual}`);
}
}
@@ -654,7 +657,7 @@ interface Array<T> {}`
invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(this.toPath(folderFullPath))!, cb => this.directoryCallback(cb, relativePath));
}
invokeFileWatcher(fileFullPath: string, eventKind: FileWatcherEventKind, useFileNameInCallback?: boolean) {
private invokeFileWatcher(fileFullPath: string, eventKind: FileWatcherEventKind, useFileNameInCallback?: boolean) {
invokeWatcherCallbacks(this.watchedFiles.get(this.toPath(fileFullPath))!, ({ cb, fileName }) => cb(useFileNameInCallback ? fileName : fileFullPath, eventKind));
}
@@ -934,7 +937,12 @@ interface Array<T> {}`
const folder = this.fs.get(base) as FsFolder;
Debug.assert(isFsFolder(folder));
this.addFileOrFolderInFolder(folder, file);
if (!this.fs.has(file.path)) {
this.addFileOrFolderInFolder(folder, file);
}
else {
this.modifyFile(path, content);
}
}
write(message: string) {
+3 -3
View File
@@ -21,8 +21,8 @@ namespace vpath {
export import relative = ts.getRelativePathFromDirectory;
export import beneath = ts.containsPath;
export import changeExtension = ts.changeAnyExtension;
export import isTypeScript = ts.hasTypeScriptFileExtension;
export import isJavaScript = ts.hasJavaScriptFileExtension;
export import isTypeScript = ts.hasTSFileExtension;
export import isJavaScript = ts.hasJSFileExtension;
const invalidRootComponentRegExp = /^(?!(\/|\/\/\w+\/|[a-zA-Z]:\/?|)$)/;
const invalidNavigableComponentRegExp = /[:*?"<>|]/;
@@ -133,4 +133,4 @@ namespace vpath {
export function isTsConfigFile(path: string): boolean {
return path.indexOf("tsconfig") !== -1 && path.indexOf("json") !== -1;
}
}
}
+5 -5
View File
@@ -21,13 +21,13 @@ namespace ts.JsTyping {
export interface CachedTyping {
typingLocation: string;
version: Semver;
version: Version;
}
/* @internal */
export function isTypingUpToDate(cachedTyping: CachedTyping, availableTypingVersions: MapLike<string>) {
const availableVersion = Semver.parse(getProperty(availableTypingVersions, `ts${versionMajorMinor}`) || getProperty(availableTypingVersions, "latest")!);
return !availableVersion.greaterThan(cachedTyping.version);
const availableVersion = new Version(getProperty(availableTypingVersions, `ts${versionMajorMinor}`) || getProperty(availableTypingVersions, "latest")!);
return availableVersion.compareTo(cachedTyping.version) <= 0;
}
/* @internal */
@@ -122,7 +122,7 @@ namespace ts.JsTyping {
// Only infer typings for .js and .jsx files
fileNames = mapDefined(fileNames, fileName => {
const path = normalizePath(fileName);
if (hasJavaScriptFileExtension(path)) {
if (hasJSFileExtension(path)) {
return path;
}
});
@@ -218,7 +218,7 @@ namespace ts.JsTyping {
*/
function getTypingNamesFromSourceFileNames(fileNames: string[]) {
const fromFileNames = mapDefined(fileNames, j => {
if (!hasJavaScriptFileExtension(j)) return undefined;
if (!hasJSFileExtension(j)) return undefined;
const inferredTypingName = removeFileExtension(getBaseFileName(j.toLowerCase()));
const cleanedTypingName = removeMinAndVersionNumbers(inferredTypingName);
-61
View File
@@ -1,61 +0,0 @@
/* @internal */
namespace ts {
function stringToInt(str: string): number {
const n = parseInt(str, 10);
if (isNaN(n)) {
throw new Error(`Error in parseInt(${JSON.stringify(str)})`);
}
return n;
}
const isPrereleaseRegex = /^(.*)-next.\d+/;
const prereleaseSemverRegex = /^(\d+)\.(\d+)\.0-next.(\d+)$/;
const semverRegex = /^(\d+)\.(\d+)\.(\d+)$/;
export class Semver {
static parse(semver: string): Semver {
const isPrerelease = isPrereleaseRegex.test(semver);
const result = Semver.tryParse(semver, isPrerelease);
if (!result) {
throw new Error(`Unexpected semver: ${semver} (isPrerelease: ${isPrerelease})`);
}
return result;
}
static fromRaw({ major, minor, patch, isPrerelease }: Semver): Semver {
return new Semver(major, minor, patch, isPrerelease);
}
// This must parse the output of `versionString`.
private static tryParse(semver: string, isPrerelease: boolean): Semver | undefined {
// Per the semver spec <http://semver.org/#spec-item-2>:
// "A normal version number MUST take the form X.Y.Z where X, Y, and Z are non-negative integers, and MUST NOT contain leading zeroes."
const rgx = isPrerelease ? prereleaseSemverRegex : semverRegex;
const match = rgx.exec(semver);
return match ? new Semver(stringToInt(match[1]), stringToInt(match[2]), stringToInt(match[3]), isPrerelease) : undefined;
}
private constructor(
readonly major: number, readonly minor: number, readonly patch: number,
/**
* If true, this is `major.minor.0-next.patch`.
* If false, this is `major.minor.patch`.
*/
readonly isPrerelease: boolean) { }
get versionString(): string {
return this.isPrerelease ? `${this.major}.${this.minor}.0-next.${this.patch}` : `${this.major}.${this.minor}.${this.patch}`;
}
equals(sem: Semver): boolean {
return this.major === sem.major && this.minor === sem.minor && this.patch === sem.patch && this.isPrerelease === sem.isPrerelease;
}
greaterThan(sem: Semver): boolean {
return this.major > sem.major || this.major === sem.major
&& (this.minor > sem.minor || this.minor === sem.minor
&& (!this.isPrerelease && sem.isPrerelease || this.isPrerelease === sem.isPrerelease
&& this.patch > sem.patch));
}
}
}
+1
View File
@@ -4,6 +4,7 @@ namespace ts.server {
export const ActionSet: ActionSet = "action::set";
export const ActionInvalidate: ActionInvalidate = "action::invalidate";
export const ActionPackageInstalled: ActionPackageInstalled = "action::packageInstalled";
export const ActionValueInspected: ActionValueInspected = "action::valueInspected";
export const EventTypesRegistry: EventTypesRegistry = "event::typesRegistry";
export const EventBeginInstallTypes: EventBeginInstallTypes = "event::beginInstallTypes";
export const EventEndInstallTypes: EventEndInstallTypes = "event::endInstallTypes";
+1 -2
View File
@@ -16,7 +16,6 @@
"files": [
"shared.ts",
"types.ts",
"jsTyping.ts",
"semver.ts"
"jsTyping.ts"
]
}
+16 -3
View File
@@ -2,6 +2,7 @@ declare namespace ts.server {
export type ActionSet = "action::set";
export type ActionInvalidate = "action::invalidate";
export type ActionPackageInstalled = "action::packageInstalled";
export type ActionValueInspected = "action::valueInspected";
export type EventTypesRegistry = "event::typesRegistry";
export type EventBeginInstallTypes = "event::beginInstallTypes";
export type EventEndInstallTypes = "event::endInstallTypes";
@@ -12,7 +13,7 @@ declare namespace ts.server {
}
export interface TypingInstallerResponse {
readonly kind: ActionSet | ActionInvalidate | EventTypesRegistry | ActionPackageInstalled | EventBeginInstallTypes | EventEndInstallTypes | EventInitializationFailed;
readonly kind: ActionSet | ActionInvalidate | EventTypesRegistry | ActionPackageInstalled | ActionValueInspected | EventBeginInstallTypes | EventEndInstallTypes | EventInitializationFailed;
}
export interface TypingInstallerRequestWithProjectName {
@@ -20,7 +21,7 @@ declare namespace ts.server {
}
/* @internal */
export type TypingInstallerRequestUnion = DiscoverTypings | CloseProject | TypesRegistryRequest | InstallPackageRequest;
export type TypingInstallerRequestUnion = DiscoverTypings | CloseProject | TypesRegistryRequest | InstallPackageRequest | InspectValueRequest;
export interface DiscoverTypings extends TypingInstallerRequestWithProjectName {
readonly fileNames: string[];
@@ -47,6 +48,12 @@ declare namespace ts.server {
readonly projectRootPath: Path;
}
/* @internal */
export interface InspectValueRequest {
readonly kind: "inspectValue";
readonly options: InspectValueOptions;
}
/* @internal */
export interface TypesRegistryResponse extends TypingInstallerResponse {
readonly kind: EventTypesRegistry;
@@ -59,6 +66,12 @@ declare namespace ts.server {
readonly message: string;
}
/* @internal */
export interface InspectValueResponse {
readonly kind: ActionValueInspected;
readonly result: ValueInfo;
}
export interface InitializationFailedResponse extends TypingInstallerResponse {
readonly kind: EventInitializationFailed;
readonly message: string;
@@ -106,5 +119,5 @@ declare namespace ts.server {
}
/* @internal */
export type TypingInstallerResponseUnion = SetTypings | InvalidateCachedTypings | TypesRegistryResponse | PackageInstalledResponse | InstallTypes | InitializationFailedResponse;
export type TypingInstallerResponseUnion = SetTypings | InvalidateCachedTypings | TypesRegistryResponse | PackageInstalledResponse | InspectValueResponse | InstallTypes | InitializationFailedResponse;
}
+2 -2
View File
@@ -7,7 +7,7 @@ interface PromiseConstructor {
/**
* Creates a new Promise.
* @param executor A callback used to initialize the promise. This callback is passed two arguments:
* a resolve callback used resolve the promise with a value or the result of another promise,
* a resolve callback used to resolve the promise with a value or the result of another promise,
* and a reject callback used to reject the promise with a provided reason or error.
*/
new <T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): Promise<T>;
@@ -193,4 +193,4 @@ interface PromiseConstructor {
resolve(): Promise<void>;
}
declare var Promise: PromiseConstructor;
declare var Promise: PromiseConstructor;
+61 -1
View File
@@ -295,6 +295,66 @@ interface FunctionConstructor {
declare const Function: FunctionConstructor;
interface CallableFunction extends Function {
/**
* Calls the function with the specified object as the this value and the elements of specified array as the arguments.
* @param thisArg The object to be used as the this object.
* @param args An array of argument values to be passed to the function.
*/
apply<T, R>(this: (this: T) => R, thisArg: T): R;
apply<T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T, args: A): R;
/**
* Calls the function with the specified object as the this value and the specified rest arguments as the arguments.
* @param thisArg The object to be used as the this object.
* @param args Argument values to be passed to the function.
*/
call<T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T, ...args: A): R;
/**
* For a given function, creates a bound function that has the same body as the original function.
* The this object of the bound function is associated with the specified object, and has the specified initial parameters.
* @param thisArg The object to be used as the this object.
* @param args Arguments to bind to the parameters of the function.
*/
bind<T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T): (...args: A) => R;
bind<T, A0, A extends any[], R>(this: (this: T, arg0: A0, ...args: A) => R, thisArg: T, arg0: A0): (...args: A) => R;
bind<T, A0, A1, A extends any[], R>(this: (this: T, arg0: A0, arg1: A1, ...args: A) => R, thisArg: T, arg0: A0, arg1: A1): (...args: A) => R;
bind<T, A0, A1, A2, A extends any[], R>(this: (this: T, arg0: A0, arg1: A1, arg2: A2, ...args: A) => R, thisArg: T, arg0: A0, arg1: A1, arg2: A2): (...args: A) => R;
bind<T, A0, A1, A2, A3, A extends any[], R>(this: (this: T, arg0: A0, arg1: A1, arg2: A2, arg3: A3, ...args: A) => R, thisArg: T, arg0: A0, arg1: A1, arg2: A2, arg3: A3): (...args: A) => R;
bind<T, AX, R>(this: (this: T, ...args: AX[]) => R, thisArg: T, ...args: AX[]): (...args: AX[]) => R;
}
interface NewableFunction extends Function {
/**
* Calls the function with the specified object as the this value and the elements of specified array as the arguments.
* @param thisArg The object to be used as the this object.
* @param args An array of argument values to be passed to the function.
*/
apply<T>(this: new () => T, thisArg: T): void;
apply<T, A extends any[]>(this: new (...args: A) => T, thisArg: T, args: A): void;
/**
* Calls the function with the specified object as the this value and the specified rest arguments as the arguments.
* @param thisArg The object to be used as the this object.
* @param args Argument values to be passed to the function.
*/
call<T, A extends any[]>(this: new (...args: A) => T, thisArg: T, ...args: A): void;
/**
* For a given function, creates a bound function that has the same body as the original function.
* The this object of the bound function is associated with the specified object, and has the specified initial parameters.
* @param thisArg The object to be used as the this object.
* @param args Arguments to bind to the parameters of the function.
*/
bind<A extends any[], R>(this: new (...args: A) => R, thisArg: any): new (...args: A) => R;
bind<A0, A extends any[], R>(this: new (arg0: A0, ...args: A) => R, thisArg: any, arg0: A0): new (...args: A) => R;
bind<A0, A1, A extends any[], R>(this: new (arg0: A0, arg1: A1, ...args: A) => R, thisArg: any, arg0: A0, arg1: A1): new (...args: A) => R;
bind<A0, A1, A2, A extends any[], R>(this: new (arg0: A0, arg1: A1, arg2: A2, ...args: A) => R, thisArg: any, arg0: A0, arg1: A1, arg2: A2): new (...args: A) => R;
bind<A0, A1, A2, A3, A extends any[], R>(this: new (arg0: A0, arg1: A1, arg2: A2, arg3: A3, ...args: A) => R, thisArg: any, arg0: A0, arg1: A1, arg2: A2, arg3: A3): new (...args: A) => R;
bind<AX, R>(this: new (...args: AX[]) => R, thisArg: any, ...args: AX[]): new (...args: AX[]) => R;
}
interface IArguments {
[index: number]: any;
length: number;
@@ -1350,7 +1410,7 @@ type Readonly<T> = {
};
/**
* From T pick a set of properties K
* From T, pick a set of properties whose keys are in the union K
*/
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
File diff suppressed because it is too large Load Diff
+200 -39
View File
@@ -5,6 +5,8 @@ namespace ts.server {
// tslint:disable variable-name
export const ProjectsUpdatedInBackgroundEvent = "projectsUpdatedInBackground";
export const ProjectLoadingStartEvent = "projectLoadingStart";
export const ProjectLoadingFinishEvent = "projectLoadingFinish";
export const SurveyReady = "surveyReady";
export const LargeFileReferencedEvent = "largeFileReferenced";
export const ConfigFileDiagEvent = "configFileDiag";
@@ -18,6 +20,16 @@ namespace ts.server {
data: { openFiles: string[]; };
}
export interface ProjectLoadingStartEvent {
eventName: typeof ProjectLoadingStartEvent;
data: { project: Project; reason: string; };
}
export interface ProjectLoadingFinishEvent {
eventName: typeof ProjectLoadingFinishEvent;
data: { project: Project; };
}
export interface SurveyReady {
eventName: typeof SurveyReady;
data: { surveyId: string; };
@@ -44,6 +56,24 @@ namespace ts.server {
readonly data: ProjectInfoTelemetryEventData;
}
/* __GDPR__
"projectInfo" : {
"${include}": ["${TypeScriptCommonProperties}"],
"projectId": { "classification": "EndUserPseudonymizedInformation", "purpose": "FeatureInsight", "endpoint": "ProjectId" },
"fileStats": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"compilerOptions": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"extends": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"files": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"include": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"exclude": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"compileOnSave": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"typeAcquisition": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"configFileName": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"projectType": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"languageServiceEnabled": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"version": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
export interface ProjectInfoTelemetryEventData {
/** Cryptographically secure hash of project file location. */
readonly projectId: string;
@@ -104,7 +134,15 @@ namespace ts.server {
readonly checkJs: boolean;
}
export type ProjectServiceEvent = LargeFileReferencedEvent | SurveyReady | ProjectsUpdatedInBackgroundEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent | OpenFileInfoTelemetryEvent;
export type ProjectServiceEvent = LargeFileReferencedEvent |
SurveyReady |
ProjectsUpdatedInBackgroundEvent |
ProjectLoadingStartEvent |
ProjectLoadingFinishEvent |
ConfigFileDiagEvent |
ProjectLanguageServiceStateEvent |
ProjectInfoTelemetryEvent |
OpenFileInfoTelemetryEvent;
export type ProjectServiceEventHandler = (event: ProjectServiceEvent) => void;
@@ -291,7 +329,8 @@ namespace ts.server {
ClosedScriptInfo = "Closed Script info",
ConfigFileForInferredRoot = "Config file for the inferred project root",
FailedLookupLocation = "Directory of Failed lookup locations in module resolution",
TypeRoots = "Type root directory"
TypeRoots = "Type root directory",
NodeModulesForClosedScriptInfo = "node_modules for closed script infos in them",
}
const enum ConfigFileWatcherStatus {
@@ -353,10 +392,18 @@ namespace ts.server {
return !!(infoOrFileName as ScriptInfo).containingProjects;
}
interface ScriptInfoInNodeModulesWatcher extends FileWatcher {
refCount: number;
}
function getDetailWatchInfo(watchType: WatchType, project: Project | undefined) {
return `Project: ${project ? project.getProjectName() : ""} WatchType: ${watchType}`;
}
function isScriptInfoWatchedFromNodeModules(info: ScriptInfo) {
return !info.isScriptOpen() && info.mTime !== undefined;
}
/*@internal*/
export function updateProjectIfDirty(project: Project) {
return project.dirty && project.updateGraph();
@@ -380,6 +427,7 @@ namespace ts.server {
* Container of all known scripts
*/
private readonly filenameToScriptInfo = createMap<ScriptInfo>();
private readonly scriptInfoInNodeModulesWatchers = createMap <ScriptInfoInNodeModulesWatcher>();
/**
* Contains all the deleted script info's version information so that
* it does not reset when creating script info again
@@ -438,7 +486,7 @@ namespace ts.server {
private readonly hostConfiguration: HostConfiguration;
private safelist: SafeList = defaultTypeSafeList;
private legacySafelist: { [key: string]: string } = {};
private readonly legacySafelist = createMap<string>();
private pendingProjectUpdates = createMap<Project>();
/* @internal */
@@ -590,14 +638,14 @@ namespace ts.server {
this.safelist = raw.typesMap;
for (const key in raw.simpleMap) {
if (raw.simpleMap.hasOwnProperty(key)) {
this.legacySafelist[key] = raw.simpleMap[key].toLowerCase();
this.legacySafelist.set(key, raw.simpleMap[key].toLowerCase());
}
}
}
catch (e) {
this.logger.info(`Error loading types map: ${e}`);
this.safelist = defaultTypeSafeList;
this.legacySafelist = {};
this.legacySafelist.clear();
}
}
@@ -693,6 +741,33 @@ namespace ts.server {
this.eventHandler(event);
}
/* @internal */
sendProjectLoadingStartEvent(project: ConfiguredProject, reason: string) {
if (!this.eventHandler) {
return;
}
project.sendLoadingProjectFinish = true;
const event: ProjectLoadingStartEvent = {
eventName: ProjectLoadingStartEvent,
data: { project, reason }
};
this.eventHandler(event);
}
/* @internal */
sendProjectLoadingFinishEvent(project: ConfiguredProject) {
if (!this.eventHandler || !project.sendLoadingProjectFinish) {
return;
}
project.sendLoadingProjectFinish = false;
const event: ProjectLoadingFinishEvent = {
eventName: ProjectLoadingFinishEvent,
data: { project }
};
this.eventHandler(event);
}
/* @internal */
delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(project: Project) {
this.delayUpdateProjectGraph(project);
@@ -927,6 +1002,7 @@ namespace ts.server {
else {
this.logConfigFileWatchUpdate(project.getConfigFilePath(), project.canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.ReloadingInferredRootFiles);
project.pendingReload = ConfigFileProgramReloadLevel.Full;
project.pendingReloadReason = "Change in config file detected";
this.delayUpdateProjectGraph(project);
// As we scheduled the update on configured project graph,
// we would need to schedule the project reload for only the root of inferred projects
@@ -1447,14 +1523,14 @@ namespace ts.server {
for (const f of fileNames) {
const fileName = propertyReader.getFileName(f);
if (hasTypeScriptFileExtension(fileName)) {
if (hasTSFileExtension(fileName)) {
continue;
}
totalNonTsFileSize += this.host.getFileSize(fileName);
if (totalNonTsFileSize > maxProgramSizeForNonTsFiles || totalNonTsFileSize > availableSpace) {
this.logger.info(getExceedLimitMessage({ propertyReader, hasTypeScriptFileExtension, host: this.host }, totalNonTsFileSize));
this.logger.info(getExceedLimitMessage({ propertyReader, hasTSFileExtension, host: this.host }, totalNonTsFileSize));
// Keep the size as zero since it's disabled
return fileName;
}
@@ -1464,14 +1540,14 @@ namespace ts.server {
return;
function getExceedLimitMessage(context: { propertyReader: FilePropertyReader<any>, hasTypeScriptFileExtension: (filename: string) => boolean, host: ServerHost }, totalNonTsFileSize: number) {
function getExceedLimitMessage(context: { propertyReader: FilePropertyReader<any>, hasTSFileExtension: (filename: string) => boolean, host: ServerHost }, totalNonTsFileSize: number) {
const files = getTop5LargestFiles(context);
return `Non TS file size exceeded limit (${totalNonTsFileSize}). Largest files: ${files.map(file => `${file.name}:${file.size}`).join(", ")}`;
}
function getTop5LargestFiles({ propertyReader, hasTypeScriptFileExtension, host }: { propertyReader: FilePropertyReader<any>, hasTypeScriptFileExtension: (filename: string) => boolean, host: ServerHost }) {
function getTop5LargestFiles({ propertyReader, hasTSFileExtension, host }: { propertyReader: FilePropertyReader<any>, hasTSFileExtension: (filename: string) => boolean, host: ServerHost }) {
return fileNames.map(f => propertyReader.getFileName(f))
.filter(name => hasTypeScriptFileExtension(name))
.filter(name => hasTSFileExtension(name))
.map(name => ({ name, size: host.getFileSize!(name) })) // TODO: GH#18217
.sort((a, b) => b.size - a.size)
.slice(0, 5);
@@ -1585,22 +1661,23 @@ namespace ts.server {
}
/* @internal */
private createConfiguredProjectWithDelayLoad(configFileName: NormalizedPath) {
private createConfiguredProjectWithDelayLoad(configFileName: NormalizedPath, reason: string) {
const project = this.createConfiguredProject(configFileName);
project.pendingReload = ConfigFileProgramReloadLevel.Full;
project.pendingReloadReason = reason;
return project;
}
/* @internal */
private createAndLoadConfiguredProject(configFileName: NormalizedPath) {
private createAndLoadConfiguredProject(configFileName: NormalizedPath, reason: string) {
const project = this.createConfiguredProject(configFileName);
this.loadConfiguredProject(project);
this.loadConfiguredProject(project, reason);
return project;
}
/* @internal */
private createLoadAndUpdateConfiguredProject(configFileName: NormalizedPath) {
const project = this.createAndLoadConfiguredProject(configFileName);
private createLoadAndUpdateConfiguredProject(configFileName: NormalizedPath, reason: string) {
const project = this.createAndLoadConfiguredProject(configFileName, reason);
project.updateGraph();
return project;
}
@@ -1609,7 +1686,9 @@ namespace ts.server {
* Read the config file of the project, and update the project root file names.
*/
/* @internal */
private loadConfiguredProject(project: ConfiguredProject) {
private loadConfiguredProject(project: ConfiguredProject, reason: string) {
this.sendProjectLoadingStartEvent(project, reason);
// Read updated contents from disk
const configFilename = normalizePath(project.getConfigFilePath());
@@ -1646,6 +1725,7 @@ namespace ts.server {
};
}
project.configFileSpecs = parsedCommandLine.configFileSpecs;
project.canConfigFileJsonReportNoInputFiles = canJsonReportNoInutFiles(parsedCommandLine.raw);
project.setProjectErrors(configFileErrors);
project.updateReferences(parsedCommandLine.projectReferences);
const lastFileExceededProgramSize = this.getFilenameForExceededTotalSizeLimitForNonTsFiles(project.canonicalConfigFilePath, compilerOptions, parsedCommandLine.fileNames, fileNamePropertyReader);
@@ -1738,7 +1818,7 @@ namespace ts.server {
const configFileSpecs = project.configFileSpecs!; // TODO: GH#18217
const configFileName = project.getConfigFilePath();
const fileNamesResult = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), project.getCompilationSettings(), project.getCachedDirectoryStructureHost(), this.hostConfiguration.extraFileExtensions);
project.updateErrorOnNoInputFiles(fileNamesResult.fileNames.length !== 0);
project.updateErrorOnNoInputFiles(fileNamesResult);
this.updateNonInferredProjectFiles(project, fileNamesResult.fileNames.concat(project.getExternalFiles()), fileNamePropertyReader);
return project.updateGraph();
}
@@ -1747,7 +1827,7 @@ namespace ts.server {
* Read the config file of the project again by clearing the cache and update the project graph
*/
/* @internal */
reloadConfiguredProject(project: ConfiguredProject) {
reloadConfiguredProject(project: ConfiguredProject, reason: string) {
// At this point, there is no reason to not have configFile in the host
const host = project.getCachedDirectoryStructureHost();
@@ -1757,7 +1837,7 @@ namespace ts.server {
this.logger.info(`Reloading configured project ${configFileName}`);
// Load project from the disk
this.loadConfiguredProject(project);
this.loadConfiguredProject(project, reason);
project.updateGraph();
this.sendConfigFileDiagEvent(project, configFileName);
@@ -1923,18 +2003,97 @@ namespace ts.server {
if (!info.isDynamicOrHasMixedContent() &&
(!this.globalCacheLocationDirectoryPath ||
!startsWith(info.path, this.globalCacheLocationDirectoryPath))) {
const { fileName } = info;
info.fileWatcher = this.watchFactory.watchFilePath(
this.host,
fileName,
(fileName, eventKind, path) => this.onSourceFileChanged(fileName, eventKind, path),
PollingInterval.Medium,
info.path,
WatchType.ClosedScriptInfo
);
const indexOfNodeModules = info.path.indexOf("/node_modules/");
if (!this.host.getModifiedTime || indexOfNodeModules === -1) {
info.fileWatcher = this.watchFactory.watchFilePath(
this.host,
info.fileName,
(fileName, eventKind, path) => this.onSourceFileChanged(fileName, eventKind, path),
PollingInterval.Medium,
info.path,
WatchType.ClosedScriptInfo
);
}
else {
info.mTime = this.getModifiedTime(info);
info.fileWatcher = this.watchClosedScriptInfoInNodeModules(info.path.substr(0, indexOfNodeModules) as Path);
}
}
}
private watchClosedScriptInfoInNodeModules(dir: Path): ScriptInfoInNodeModulesWatcher {
// Watch only directory
const existing = this.scriptInfoInNodeModulesWatchers.get(dir);
if (existing) {
existing.refCount++;
return existing;
}
const watchDir = dir + "/node_modules" as Path;
const watcher = this.watchFactory.watchDirectory(
this.host,
watchDir,
(fileOrDirectory) => {
const fileOrDirectoryPath = this.toPath(fileOrDirectory);
// Has extension
Debug.assert(result.refCount > 0);
if (watchDir === fileOrDirectoryPath) {
this.refreshScriptInfosInDirectory(watchDir);
}
else {
const info = this.getScriptInfoForPath(fileOrDirectoryPath);
if (info) {
if (isScriptInfoWatchedFromNodeModules(info)) {
this.refreshScriptInfo(info);
}
}
// Folder
else if (!hasExtension(fileOrDirectoryPath)) {
this.refreshScriptInfosInDirectory(fileOrDirectoryPath);
}
}
},
WatchDirectoryFlags.Recursive,
WatchType.NodeModulesForClosedScriptInfo
);
const result: ScriptInfoInNodeModulesWatcher = {
close: () => {
if (result.refCount === 1) {
watcher.close();
this.scriptInfoInNodeModulesWatchers.delete(dir);
}
else {
result.refCount--;
}
},
refCount: 1
};
this.scriptInfoInNodeModulesWatchers.set(dir, result);
return result;
}
private getModifiedTime(info: ScriptInfo) {
return (this.host.getModifiedTime!(info.path) || missingFileModifiedTime).getTime();
}
private refreshScriptInfo(info: ScriptInfo) {
const mTime = this.getModifiedTime(info);
if (mTime !== info.mTime) {
const eventKind = getFileWatcherEventKind(info.mTime!, mTime);
info.mTime = mTime;
this.onSourceFileChanged(info.fileName, eventKind, info.path);
}
}
private refreshScriptInfosInDirectory(dir: Path) {
dir = dir + directorySeparator as Path;
this.filenameToScriptInfo.forEach(info => {
if (isScriptInfoWatchedFromNodeModules(info) && startsWith(info.path, dir)) {
this.refreshScriptInfo(info);
}
});
}
private stopWatchingScriptInfo(info: ScriptInfo) {
if (info.fileWatcher) {
info.fileWatcher.close();
@@ -2031,7 +2190,6 @@ namespace ts.server {
if (project.hasExternalProjectRef() &&
project.pendingReload === ConfigFileProgramReloadLevel.Full &&
!this.pendingProjectUpdates.has(project.getProjectName())) {
this.loadConfiguredProject(project);
project.updateGraph();
}
});
@@ -2063,7 +2221,7 @@ namespace ts.server {
// as there is no need to load contents of the files from the disk
// Reload Projects
this.reloadConfiguredProjectForFiles(this.openFiles, /*delayReload*/ false, returnTrue);
this.reloadConfiguredProjectForFiles(this.openFiles, /*delayReload*/ false, returnTrue, "User requested reload projects");
this.ensureProjectForOpenFiles();
}
@@ -2074,7 +2232,8 @@ namespace ts.server {
/*delayReload*/ true,
ignoreIfNotRootOfInferredProject ?
isRootOfInferredProject => isRootOfInferredProject : // Reload open files if they are root of inferred project
returnTrue // Reload all the open files impacted by config file
returnTrue, // Reload all the open files impacted by config file
"Change in config file detected"
);
this.delayEnsureProjectForOpenFiles();
}
@@ -2086,7 +2245,7 @@ namespace ts.server {
* If the there is no existing project it just opens the configured project for the config file
* reloadForInfo provides a way to filter out files to reload configured project for
*/
private reloadConfiguredProjectForFiles<T>(openFiles: Map<T>, delayReload: boolean, shouldReloadProjectFor: (openFileValue: T) => boolean) {
private reloadConfiguredProjectForFiles<T>(openFiles: Map<T>, delayReload: boolean, shouldReloadProjectFor: (openFileValue: T) => boolean, reason: string) {
const updatedProjects = createMap<true>();
// try to reload config file for all open files
openFiles.forEach((openFileValue, path) => {
@@ -2107,11 +2266,12 @@ namespace ts.server {
if (!updatedProjects.has(configFileName)) {
if (delayReload) {
project.pendingReload = ConfigFileProgramReloadLevel.Full;
project.pendingReloadReason = reason;
this.delayUpdateProjectGraph(project);
}
else {
// reload from the disk
this.reloadConfiguredProject(project);
this.reloadConfiguredProject(project, reason);
}
updatedProjects.set(configFileName, true);
}
@@ -2198,7 +2358,8 @@ namespace ts.server {
const configFileName = this.getConfigFileNameForFile(originalFileInfo);
if (!configFileName) return undefined;
const configuredProject = this.findConfiguredProjectByProjectName(configFileName) || this.createAndLoadConfiguredProject(configFileName);
const configuredProject = this.findConfiguredProjectByProjectName(configFileName) ||
this.createAndLoadConfiguredProject(configFileName, `Creating project for original file: ${originalFileInfo.fileName} for location: ${location.fileName}`);
updateProjectIfDirty(configuredProject);
// Keep this configured project as referenced from project
addOriginalConfiguredProject(configuredProject);
@@ -2248,7 +2409,7 @@ namespace ts.server {
if (configFileName) {
project = this.findConfiguredProjectByProjectName(configFileName);
if (!project) {
project = this.createLoadAndUpdateConfiguredProject(configFileName);
project = this.createLoadAndUpdateConfiguredProject(configFileName, `Creating possible configured project for ${fileName} to open`);
// Send the event only if the project got created as part of this open request and info is part of the project
if (info.isOrphan()) {
// Since the file isnt part of configured project, do not send config file info
@@ -2560,13 +2721,13 @@ namespace ts.server {
if (fileExtensionIs(baseName, "js")) {
const inferredTypingName = removeFileExtension(baseName);
const cleanedTypingName = removeMinAndVersionNumbers(inferredTypingName);
if (this.legacySafelist[cleanedTypingName]) {
const typeName = this.legacySafelist.get(cleanedTypingName);
if (typeName !== undefined) {
this.logger.info(`Excluded '${normalizedNames[i]}' because it matched ${cleanedTypingName} from the legacy safelist`);
excludedFiles.push(normalizedNames[i]);
// *exclude* it from the project...
exclude = true;
// ... but *include* it in the list of types to acquire
const typeName = this.legacySafelist[cleanedTypingName];
// Same best-effort dedupe as above
if (typeAcqInclude.indexOf(typeName) < 0) {
typeAcqInclude.push(typeName);
@@ -2689,8 +2850,8 @@ namespace ts.server {
if (!project) {
// errors are stored in the project, do not need to update the graph
project = this.getHostPreferences().lazyConfiguredProjectsFromExternalProject ?
this.createConfiguredProjectWithDelayLoad(tsconfigFile) :
this.createLoadAndUpdateConfiguredProject(tsconfigFile);
this.createConfiguredProjectWithDelayLoad(tsconfigFile, `Creating configured project in external project: ${proj.projectFileName}`) :
this.createLoadAndUpdateConfiguredProject(tsconfigFile, `Creating configured project in external project: ${proj.projectFileName}`);
}
if (project && !contains(exisingConfigFiles, tsconfigFile)) {
// keep project alive even if no documents are opened - its lifetime is bound to the lifetime of containing external project
+26 -11
View File
@@ -254,6 +254,11 @@ namespace ts.server {
installPackage(options: InstallPackageOptions): Promise<ApplyCodeActionCommandResult> {
return this.typingsCache.installPackage({ ...options, projectName: this.projectName, projectRootPath: this.toPath(this.currentDirectory) });
}
/* @internal */
inspectValue(options: InspectValueOptions): Promise<ValueInfo> {
return this.typingsCache.inspectValue(options);
}
private get typingsCache(): TypingsCache {
return this.projectService.typingsCache;
}
@@ -352,6 +357,10 @@ namespace ts.server {
return this.projectService.host.readFile(fileName);
}
writeFile(fileName: string, content: string): void {
return this.projectService.host.writeFile(fileName, content);
}
fileExists(file: string): boolean {
// As an optimization, don't hit the disks for files we already know don't exist
// (because we're watching for their creation).
@@ -574,7 +583,7 @@ namespace ts.server {
for (const f of this.program.getSourceFiles()) {
this.detachScriptInfoIfNotRoot(f.fileName);
}
const projectReferences = this.program.getProjectReferences();
const projectReferences = this.program.getResolvedProjectReferences();
if (projectReferences) {
for (const ref of projectReferences) {
if (ref) {
@@ -653,7 +662,7 @@ namespace ts.server {
return this.rootFiles;
}
return map(this.program.getSourceFiles(), sourceFile => {
const scriptInfo = this.projectService.getScriptInfoForPath(sourceFile.resolvedPath || sourceFile.path);
const scriptInfo = this.projectService.getScriptInfoForPath(sourceFile.resolvedPath);
Debug.assert(!!scriptInfo, "getScriptInfo", () => `scriptInfo for a file '${sourceFile.fileName}' Path: '${sourceFile.path}' / '${sourceFile.resolvedPath}' is missing.`);
return scriptInfo!;
});
@@ -1311,10 +1320,15 @@ namespace ts.server {
/* @internal */
pendingReload: ConfigFileProgramReloadLevel;
/* @internal */
pendingReloadReason: string | undefined;
/*@internal*/
configFileSpecs: ConfigFileSpecs | undefined;
/*@internal*/
canConfigFileJsonReportNoInputFiles: boolean;
/** Ref count to the project when opened from external project */
private externalProjectRefCount = 0;
@@ -1327,6 +1341,9 @@ namespace ts.server {
protected isInitialLoadPending: () => boolean = returnTrue;
/*@internal*/
sendLoadingProjectFinish = false;
/*@internal*/
constructor(configFileName: NormalizedPath,
projectService: ProjectService,
@@ -1359,12 +1376,15 @@ namespace ts.server {
result = this.projectService.reloadFileNamesOfConfiguredProject(this);
break;
case ConfigFileProgramReloadLevel.Full:
this.projectService.reloadConfiguredProject(this);
const reason = Debug.assertDefined(this.pendingReloadReason);
this.pendingReloadReason = undefined;
this.projectService.reloadConfiguredProject(this, reason);
result = true;
break;
default:
result = super.updateGraph();
}
this.projectService.sendProjectLoadingFinishEvent(this);
this.projectService.sendProjectTelemetry(this);
this.projectService.sendSurveyReady(this);
return result;
@@ -1390,7 +1410,7 @@ namespace ts.server {
/*@internal*/
getResolvedProjectReferences() {
const program = this.getCurrentProgram();
return program && program.getProjectReferences();
return program && program.getResolvedProjectReferences();
}
enablePlugins() {
@@ -1531,13 +1551,8 @@ namespace ts.server {
}
/*@internal*/
updateErrorOnNoInputFiles(hasFileNames: boolean) {
if (hasFileNames) {
filterMutate(this.projectErrors!, error => !isErrorNoInputFiles(error)); // TODO: GH#18217
}
else if (!this.configFileSpecs!.filesSpecs && !some(this.projectErrors, isErrorNoInputFiles)) { // TODO: GH#18217
this.projectErrors!.push(getErrorForNoInputFiles(this.configFileSpecs!, this.getConfigFilePath()));
}
updateErrorOnNoInputFiles(fileNameResult: ExpandResult) {
updateErrorForNoInputFiles(fileNameResult, this.getConfigFilePath(), this.configFileSpecs!, this.projectErrors!, this.canConfigFileJsonReportNoInputFiles);
}
}
+54 -6
View File
@@ -1087,16 +1087,17 @@ namespace ts.server.protocol {
/**
* Information about the item to be renamed.
*/
export interface RenameInfo {
export type RenameInfo = RenameInfoSuccess | RenameInfoFailure;
export interface RenameInfoSuccess {
/**
* True if item can be renamed.
*/
canRename: boolean;
canRename: true;
/**
* Error message if item can not be renamed.
* File or directory to rename.
* If set, `getEditsForFileRename` should be called instead of `findRenameLocations`.
*/
localizedErrorMessage?: string;
fileToRename?: string;
/**
* Display name of the item to be renamed.
@@ -1117,6 +1118,16 @@ namespace ts.server.protocol {
* Optional modifiers for the kind (such as 'public').
*/
kindModifiers: string;
/** Span of text to rename. */
triggerSpan: TextSpan;
}
export interface RenameInfoFailure {
canRename: false;
/**
* Error message if item can not be renamed.
*/
localizedErrorMessage: string;
}
/**
@@ -1126,7 +1137,12 @@ namespace ts.server.protocol {
/** The file to which the spans apply */
file: string;
/** The text spans in this group */
locs: TextSpan[];
locs: RenameTextSpan[];
}
export interface RenameTextSpan extends TextSpan {
readonly prefixText?: string;
readonly suffixText?: string;
}
export interface RenameResponseBody {
@@ -2436,6 +2452,30 @@ namespace ts.server.protocol {
openFiles: string[];
}
export type ProjectLoadingStartEventName = "projectLoadingStart";
export interface ProjectLoadingStartEvent extends Event {
event: ProjectLoadingStartEventName;
body: ProjectLoadingStartEventBody;
}
export interface ProjectLoadingStartEventBody {
/** name of the project */
projectName: string;
/** reason for loading */
reason: string;
}
export type ProjectLoadingFinishEventName = "projectLoadingFinish";
export interface ProjectLoadingFinishEvent extends Event {
event: ProjectLoadingFinishEventName;
body: ProjectLoadingFinishEventBody;
}
export interface ProjectLoadingFinishEventBody {
/** name of the project */
projectName: string;
}
export type SurveyReadyEventName = "surveyReady";
export interface SurveyReadyEvent extends Event {
@@ -2728,6 +2768,14 @@ namespace ts.server.protocol {
payload: TypingsInstalledTelemetryEventPayload;
}
/* __GDPR__
"typingsinstalled" : {
"${include}": ["${TypeScriptCommonProperties}"],
"installedPackages": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
"installSuccess": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"typingsInstallerVersion": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
export interface TypingsInstalledTelemetryEventPayload {
/**
* Comma separated list of installed typing packages
+4 -1
View File
@@ -167,7 +167,7 @@ namespace ts.server {
const fileName = tempFileName || this.fileName;
const getText = () => text === undefined ? (text = this.host.readFile(fileName) || "") : text;
// Only non typescript files have size limitation
if (!hasTypeScriptFileExtension(this.fileName)) {
if (!hasTSFileExtension(this.fileName)) {
const fileSize = this.host.getFileSize ? this.host.getFileSize(fileName) : getText().length;
if (fileSize > maxFileSize) {
Debug.assert(!!this.info.containingProjects.length);
@@ -250,6 +250,9 @@ namespace ts.server {
/*@internal*/
cacheSourceFile: DocumentRegistrySourceFileCache;
/*@internal*/
mTime?: number;
constructor(
private readonly host: ServerHost,
readonly fileName: NormalizedPath,
+74 -24
View File
@@ -336,16 +336,23 @@ namespace ts.server {
function combineProjectOutputForReferences(projects: Projects, defaultProject: Project, initialLocation: sourcemaps.SourceMappableLocation, projectService: ProjectService): ReadonlyArray<ReferencedSymbol> {
const outputs: ReferencedSymbol[] = [];
combineProjectOutputWorker<sourcemaps.SourceMappableLocation>(projects, defaultProject, initialLocation, projectService, ({ project, location }, tryAddToTodo) => {
combineProjectOutputWorker<sourcemaps.SourceMappableLocation>(projects, defaultProject, initialLocation, projectService, ({ project, location }, getMappedLocation) => {
for (const outputReferencedSymbol of project.getLanguageService().findReferences(location.fileName, location.position) || emptyArray) {
let symbolToAddTo = find(outputs, o => documentSpansEqual(o.definition, outputReferencedSymbol.definition));
const mappedDefinitionFile = getMappedLocation(project, documentSpanLocation(outputReferencedSymbol.definition));
const definition: ReferencedSymbolDefinitionInfo = mappedDefinitionFile === undefined ? outputReferencedSymbol.definition : {
...outputReferencedSymbol.definition,
textSpan: createTextSpan(mappedDefinitionFile.position, outputReferencedSymbol.definition.textSpan.length),
fileName: mappedDefinitionFile.fileName,
};
let symbolToAddTo = find(outputs, o => documentSpansEqual(o.definition, definition));
if (!symbolToAddTo) {
symbolToAddTo = { definition: outputReferencedSymbol.definition, references: [] };
symbolToAddTo = { definition, references: [] };
outputs.push(symbolToAddTo);
}
for (const ref of outputReferencedSymbol.references) {
if (!contains(symbolToAddTo.references, ref, documentSpansEqual) && !tryAddToTodo(project, documentSpanLocation(ref))) {
// If it's in a mapped file, that is added to the todo list by `getMappedLocation`.
if (!contains(symbolToAddTo.references, ref, documentSpansEqual) && !getMappedLocation(project, documentSpanLocation(ref))) {
symbolToAddTo.references.push(ref);
}
}
@@ -373,12 +380,17 @@ namespace ts.server {
}
}
type CombineProjectOutputCallback<TLocation extends sourcemaps.SourceMappableLocation | undefined> = (
where: ProjectAndLocation<TLocation>,
getMappedLocation: (project: Project, location: sourcemaps.SourceMappableLocation) => sourcemaps.SourceMappableLocation | undefined,
) => void;
function combineProjectOutputWorker<TLocation extends sourcemaps.SourceMappableLocation | undefined>(
projects: Projects,
defaultProject: Project,
initialLocation: TLocation,
projectService: ProjectService,
cb: (where: ProjectAndLocation<TLocation>, getMappedLocation: (project: Project, location: sourcemaps.SourceMappableLocation) => boolean) => void,
cb: CombineProjectOutputCallback<TLocation>,
getDefinition: (() => sourcemaps.SourceMappableLocation | undefined) | undefined,
): void {
let toDo: ProjectAndLocation<TLocation>[] | undefined;
@@ -417,13 +429,13 @@ namespace ts.server {
projectService: ProjectService,
toDo: ProjectAndLocation<TLocation>[] | undefined,
seenProjects: Map<true>,
cb: (where: ProjectAndLocation<TLocation>, getMappedLocation: (project: Project, location: sourcemaps.SourceMappableLocation) => boolean) => void,
cb: CombineProjectOutputCallback<TLocation>,
): ProjectAndLocation<TLocation>[] | undefined {
if (projectAndLocation.project.getCancellationToken().isCancellationRequested()) return undefined; // Skip rest of toDo if cancelled
cb(projectAndLocation, (project, location) => {
seenProjects.set(projectAndLocation.project.projectName, true);
const originalLocation = projectService.getOriginalLocationEnsuringConfiguredProject(project, location);
if (!originalLocation) return false;
if (!originalLocation) return undefined;
const originalScriptInfo = projectService.getScriptInfo(originalLocation.fileName)!;
toDo = toDo || [];
@@ -437,7 +449,7 @@ namespace ts.server {
for (const symlinkedProject of symlinkedProjects) addToTodo({ project: symlinkedProject, location: originalLocation as TLocation }, toDo!, seenProjects);
});
}
return true;
return originalLocation;
});
return toDo;
}
@@ -478,6 +490,7 @@ namespace ts.server {
globalPlugins?: ReadonlyArray<string>;
pluginProbeLocations?: ReadonlyArray<string>;
allowLocalPluginLoads?: boolean;
typesMapLocation?: string;
}
export class Session implements EventSender {
@@ -538,6 +551,7 @@ namespace ts.server {
globalPlugins: opts.globalPlugins,
pluginProbeLocations: opts.pluginProbeLocations,
allowLocalPluginLoads: opts.allowLocalPluginLoads,
typesMapLocation: opts.typesMapLocation,
syntaxOnly: opts.syntaxOnly,
};
this.projectService = new ProjectService(settings);
@@ -554,9 +568,19 @@ namespace ts.server {
const { openFiles } = event.data;
this.projectsUpdatedInBackgroundEvent(openFiles);
break;
case ProjectLoadingStartEvent:
const { project, reason } = event.data;
this.event<protocol.ProjectLoadingStartEventBody>(
{ projectName: project.getProjectName(), reason },
ProjectLoadingStartEvent);
break;
case ProjectLoadingFinishEvent:
const { project: finishProject } = event.data;
this.event<protocol.ProjectLoadingFinishEventBody>({ projectName: finishProject.getProjectName() }, ProjectLoadingStartEvent);
break;
case LargeFileReferencedEvent:
const { file, fileSize, maxFileSize } = event.data;
this.event<protocol.LargeFileReferencedEventBody>({ file, fileSize, maxFileSize }, "largeFileReferenced");
this.event<protocol.LargeFileReferencedEventBody>({ file, fileSize, maxFileSize }, LargeFileReferencedEvent);
break;
case ConfigFileDiagEvent:
const { triggerFile, configFileName: configFile, diagnostics } = event.data;
@@ -565,14 +589,14 @@ namespace ts.server {
triggerFile,
configFile,
diagnostics: bakedDiags
}, "configFileDiag");
}, ConfigFileDiagEvent);
break;
case SurveyReady:
const { surveyId } = event.data;
this.event<protocol.SurveyReadyEventBody>({ surveyId }, "surveyReady");
this.event<protocol.SurveyReadyEventBody>({ surveyId }, SurveyReady);
break;
case ProjectLanguageServiceStateEvent: {
const eventName: protocol.ProjectLanguageServiceStateEventName = "projectLanguageServiceState";
const eventName: protocol.ProjectLanguageServiceStateEventName = ProjectLanguageServiceStateEvent;
this.event<protocol.ProjectLanguageServiceStateEventBody>({
projectName: event.data.project.getProjectName(),
languageServiceEnabled: event.data.languageServiceEnabled
@@ -603,11 +627,15 @@ namespace ts.server {
// Send project changed event
this.event<protocol.ProjectsUpdatedInBackgroundEventBody>({
openFiles
}, "projectsUpdatedInBackground");
}, ProjectsUpdatedInBackgroundEvent);
}
}
public logError(err: Error, cmd: string) {
public logError(err: Error, cmd: string): void {
this.logErrorWorker(err, cmd);
}
private logErrorWorker(err: Error, cmd: string, fileRequest?: protocol.FileRequestArgs): void {
let msg = "Exception on executing command " + cmd;
if (err.message) {
msg += ":\n" + indent(err.message);
@@ -615,6 +643,19 @@ namespace ts.server {
msg += "\n" + indent((<StackTraceError>err).stack!);
}
}
if (fileRequest && this.logger.hasLevel(LogLevel.verbose)) {
try {
const { file, project } = this.getFileAndProject(fileRequest);
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
if (scriptInfo) {
const text = getSnapshotText(scriptInfo.getSnapshot());
msg += `\n\nFile text of ${fileRequest.file}:${indent(text)}\n`;
}
}
catch {} // tslint:disable-line no-empty
}
this.logger.msg(msg, Msg.Err);
}
@@ -1096,7 +1137,7 @@ namespace ts.server {
return projectInfo;
}
private getRenameInfo(args: protocol.FileLocationRequestArgs) {
private getRenameInfo(args: protocol.FileLocationRequestArgs): RenameInfo {
const { file, project } = this.getFileAndProject(args);
const position = this.getPositionInFile(args, file);
return project.getLanguageService().getRenameInfo(file, position);
@@ -1149,21 +1190,28 @@ namespace ts.server {
if (!simplifiedResult) return locations;
const defaultProject = this.getDefaultProject(args);
const renameInfo = Session.mapRenameInfo(defaultProject.getLanguageService().getRenameInfo(file, position));
const renameInfo: protocol.RenameInfo = this.mapRenameInfo(defaultProject.getLanguageService().getRenameInfo(file, position), Debug.assertDefined(this.projectService.getScriptInfo(file)));
return { info: renameInfo, locs: this.toSpanGroups(locations) };
}
// strips 'triggerSpan'
private static mapRenameInfo({ canRename, localizedErrorMessage, displayName, fullDisplayName, kind, kindModifiers }: RenameInfo): protocol.RenameInfo {
return { canRename, localizedErrorMessage, displayName, fullDisplayName, kind, kindModifiers };
private mapRenameInfo(info: RenameInfo, scriptInfo: ScriptInfo): protocol.RenameInfo {
if (info.canRename) {
const { canRename, fileToRename, displayName, fullDisplayName, kind, kindModifiers, triggerSpan } = info;
return identity<protocol.RenameInfoSuccess>(
{ canRename, fileToRename, displayName, fullDisplayName, kind, kindModifiers, triggerSpan: this.toLocationTextSpan(triggerSpan, scriptInfo) });
}
else {
return info;
}
}
private toSpanGroups(locations: ReadonlyArray<RenameLocation>): ReadonlyArray<protocol.SpanGroup> {
const map = createMap<protocol.SpanGroup>();
for (const { fileName, textSpan } of locations) {
for (const { fileName, textSpan, originalTextSpan: _, originalFileName: _1, ...prefixSuffixText } of locations) {
let group = map.get(fileName);
if (!group) map.set(fileName, group = { file: fileName, locs: [] });
group.locs.push(this.toLocationTextSpan(textSpan, Debug.assertDefined(this.projectService.getScriptInfo(fileName))));
const scriptInfo = Debug.assertDefined(this.projectService.getScriptInfo(fileName));
group.locs.push({ ...this.toLocationTextSpan(textSpan, scriptInfo), ...prefixSuffixText });
}
return arrayFrom(map.values());
}
@@ -1818,8 +1866,8 @@ namespace ts.server {
private applyCodeActionCommand(args: protocol.ApplyCodeActionCommandRequestArgs): {} {
const commands = args.command as CodeActionCommand | CodeActionCommand[]; // They should be sending back the command we sent them.
for (const command of toArray(commands)) {
const { project } = this.getFileAndProject(command);
project.getLanguageService().applyCodeActionCommand(command).then(
const { file, project } = this.getFileAndProject(command);
project.getLanguageService().applyCodeActionCommand(command, this.getFormatOptions(file)).then(
_result => { /* TODO: GH#20447 report success message? */ },
_error => { /* TODO: GH#20447 report errors */ });
}
@@ -2310,8 +2358,10 @@ namespace ts.server {
}
let request: protocol.Request | undefined;
let relevantFile: protocol.FileRequestArgs | undefined;
try {
request = <protocol.Request>JSON.parse(message);
relevantFile = request.arguments && (request as protocol.FileRequest).arguments.file ? (request as protocol.FileRequest).arguments : undefined;
const { response, responseRequired } = this.executeCommand(request);
if (this.logger.hasLevel(LogLevel.requestTime)) {
@@ -2337,7 +2387,7 @@ namespace ts.server {
this.doOutput({ canceled: true }, request!.command, request!.seq, /*success*/ true);
return;
}
this.logError(err, message);
this.logErrorWorker(err, message, relevantFile);
this.doOutput(
/*info*/ undefined,
request ? request.command : CommandNames.Unknown,
+7
View File
@@ -8,6 +8,8 @@ namespace ts.server {
export interface ITypingsInstaller {
isKnownTypesPackageName(name: string): boolean;
installPackage(options: InstallPackageOptionsWithProject): Promise<ApplyCodeActionCommandResult>;
/* @internal */
inspectValue(options: InspectValueOptions): Promise<ValueInfo>;
enqueueInstallTypingsRequest(p: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string> | undefined): void;
attach(projectService: ProjectService): void;
onProjectClosed(p: Project): void;
@@ -18,6 +20,7 @@ namespace ts.server {
isKnownTypesPackageName: returnFalse,
// Should never be called because we never provide a types registry.
installPackage: notImplemented,
inspectValue: notImplemented,
enqueueInstallTypingsRequest: noop,
attach: noop,
onProjectClosed: noop,
@@ -95,6 +98,10 @@ namespace ts.server {
return this.installer.installPackage(options);
}
inspectValue(options: InspectValueOptions): Promise<ValueInfo> {
return this.installer.inspectValue(options);
}
enqueueInstallTypingsForProject(project: Project, unresolvedImports: SortedReadonlyArray<string> | undefined, forceRefresh: boolean) {
const typeAcquisition = project.getTypeAcquisition();
+4 -2
View File
@@ -246,14 +246,16 @@ namespace ts.server {
return index === 0 || value !== array[index - 1];
}
const indentStr = "\n ";
/* @internal */
export function indent(str: string): string {
return "\n " + str;
return indentStr + str.replace(/\n/g, indentStr);
}
/** Put stringified JSON on the next line, indented. */
/* @internal */
export function stringifyIndented(json: {}): string {
return "\n " + JSON.stringify(json);
return indentStr + JSON.stringify(json);
}
}
@@ -138,7 +138,7 @@ namespace ts.codefix {
default: {
// Don't try to declare members in JavaScript files
if (isSourceFileJavaScript(sourceFile)) {
if (isSourceFileJS(sourceFile)) {
return;
}
const prop = createProperty(/*decorators*/ undefined, modifiers, memberDeclaration.name, /*questionToken*/ undefined,
+174 -131
View File
@@ -2,42 +2,37 @@
namespace ts.codefix {
const fixId = "convertToAsyncFunction";
const errorCodes = [Diagnostics.This_may_be_converted_to_an_async_function.code];
let codeActionSucceeded = true;
registerCodeFix({
errorCodes,
getCodeActions(context: CodeFixContext) {
codeActionSucceeded = true;
const changes = textChanges.ChangeTracker.with(context, (t) => convertToAsyncFunction(t, context.sourceFile, context.span.start, context.program.getTypeChecker(), context));
return [createCodeFixAction(fixId, changes, Diagnostics.Convert_to_async_function, fixId, Diagnostics.Convert_all_to_async_functions)];
return codeActionSucceeded ? [createCodeFixAction(fixId, changes, Diagnostics.Convert_to_async_function, fixId, Diagnostics.Convert_all_to_async_functions)] : [];
},
fixIds: [fixId],
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, err) => convertToAsyncFunction(changes, err.file, err.start, context.program.getTypeChecker(), context)),
});
/*
custom type to encapsulate information for variable declarations synthesized in the refactor
numberOfUsesOriginal - number of times the variable should be assigned in the refactor
numberOfUsesSynthesized - count of how many times the variable has been assigned so far
At the end of the refactor, numberOfUsesOriginal should === numberOfUsesSynthesized
*/
interface SynthIdentifier {
identifier: Identifier;
types: Type[];
numberOfAssignmentsOriginal: number;
readonly identifier: Identifier;
readonly types: Type[];
numberOfAssignmentsOriginal: number; // number of times the variable should be assigned in the refactor
}
interface SymbolAndIdentifier {
identifier: Identifier;
symbol: Symbol;
readonly identifier: Identifier;
readonly symbol: Symbol;
}
interface Transformer {
checker: TypeChecker;
synthNamesMap: Map<SynthIdentifier>; // keys are the symbol id of the identifier
allVarNames: SymbolAndIdentifier[];
setOfExpressionsToReturn: Map<true>; // keys are the node ids of the expressions
constIdentifiers: Identifier[];
originalTypeMap: Map<Type>; // keys are the node id of the identifier
isInJSFile: boolean;
readonly checker: TypeChecker;
readonly synthNamesMap: Map<SynthIdentifier>; // keys are the symbol id of the identifier
readonly allVarNames: ReadonlyArray<SymbolAndIdentifier>;
readonly setOfExpressionsToReturn: ReadonlyMap<true>; // keys are the node ids of the expressions
readonly constIdentifiers: Identifier[];
readonly originalTypeMap: ReadonlyMap<Type>; // keys are the node id of the identifier
readonly isInJSFile: boolean;
}
function convertToAsyncFunction(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker, context: CodeFixContextBase): void {
@@ -61,19 +56,19 @@ namespace ts.codefix {
const synthNamesMap: Map<SynthIdentifier> = createMap();
const originalTypeMap: Map<Type> = createMap();
const allVarNames: SymbolAndIdentifier[] = [];
const isInJSFile = isInJavaScriptFile(functionToConvert);
const isInJavascript = isInJSFile(functionToConvert);
const setOfExpressionsToReturn = getAllPromiseExpressionsToReturn(functionToConvert, checker);
const functionToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(functionToConvert, checker, synthNamesMap, context, setOfExpressionsToReturn, originalTypeMap, allVarNames);
const constIdentifiers = getConstIdentifiers(synthNamesMap);
const returnStatements = getReturnStatementsWithPromiseHandlers(functionToConvertRenamed);
const transformer = { checker, synthNamesMap, allVarNames, setOfExpressionsToReturn, constIdentifiers, originalTypeMap, isInJSFile };
const transformer: Transformer = { checker, synthNamesMap, allVarNames, setOfExpressionsToReturn, constIdentifiers, originalTypeMap, isInJSFile: isInJavascript };
if (!returnStatements.length) {
return;
}
// add the async keyword
changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, functionToConvert);
changes.insertLastModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, functionToConvert);
function startTransformation(node: CallExpression, nodeToReplace: Node) {
const newNodes = transformExpression(node, transformer, node);
@@ -81,24 +76,19 @@ namespace ts.codefix {
}
for (const statement of returnStatements) {
if (isCallExpression(statement)) {
startTransformation(statement, statement);
}
else {
forEachChild(statement, function visit(node: Node) {
if (isCallExpression(node)) {
startTransformation(node, statement);
}
else if (!isFunctionLike(node)) {
forEachChild(node, visit);
}
});
}
forEachChild(statement, function visit(node) {
if (isCallExpression(node)) {
startTransformation(node, statement);
}
else if (!isFunctionLike(node)) {
forEachChild(node, visit);
}
});
}
}
// Returns the identifiers that are never reassigned in the refactor
function getConstIdentifiers(synthNamesMap: Map<SynthIdentifier>): Identifier[] {
function getConstIdentifiers(synthNamesMap: ReadonlyMap<SynthIdentifier>): Identifier[] {
const constIdentifiers: Identifier[] = [];
synthNamesMap.forEach((val) => {
if (val.numberOfAssignmentsOriginal === 0) {
@@ -165,6 +155,7 @@ namespace ts.codefix {
function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, synthNamesMap: Map<SynthIdentifier>, context: CodeFixContextBase, setOfAllExpressionsToReturn: Map<true>, originalType: Map<Type>, allVarNames: SymbolAndIdentifier[]): FunctionLikeDeclaration {
const identsToRenameMap: Map<Identifier> = createMap(); // key is the symbol id
const collidingSymbolMap: Map<Symbol[]> = createMap();
forEachChild(nodeToRename, function visit(node: Node) {
if (!isIdentifier(node)) {
forEachChild(node, visit);
@@ -181,20 +172,26 @@ namespace ts.codefix {
// if the identifier refers to a function we want to add the new synthesized variable for the declaration (ex. blob in let blob = res(arg))
// Note - the choice of the last call signature is arbitrary
if (lastCallSignature && lastCallSignature.parameters.length && !synthNamesMap.has(symbolIdString)) {
const synthName = getNewNameIfConflict(createIdentifier(lastCallSignature.parameters[0].name), allVarNames);
if (lastCallSignature && !isFunctionLikeDeclaration(node.parent) && !synthNamesMap.has(symbolIdString)) {
const firstParameter = firstOrUndefined(lastCallSignature.parameters);
const ident = firstParameter && isParameter(firstParameter.valueDeclaration) && tryCast(firstParameter.valueDeclaration.name, isIdentifier) || createOptimisticUniqueName("result");
const synthName = getNewNameIfConflict(ident, collidingSymbolMap);
synthNamesMap.set(symbolIdString, synthName);
allVarNames.push({ identifier: synthName.identifier, symbol });
addNameToFrequencyMap(collidingSymbolMap, ident.text, symbol);
}
// we only care about identifiers that are parameters and declarations (don't care about other uses)
else if (node.parent && (isParameter(node.parent) || isVariableDeclaration(node.parent))) {
const originalName = node.text;
const collidingSymbols = collidingSymbolMap.get(originalName);
// if the identifier name conflicts with a different identifier that we've already seen
if (allVarNames.some(ident => ident.identifier.text === node.text && ident.symbol !== symbol)) {
const newName = getNewNameIfConflict(node, allVarNames);
if (collidingSymbols && collidingSymbols.some(prevSymbol => prevSymbol !== symbol)) {
const newName = getNewNameIfConflict(node, collidingSymbolMap);
identsToRenameMap.set(symbolIdString, newName.identifier);
synthNamesMap.set(symbolIdString, newName);
allVarNames.push({ identifier: newName.identifier, symbol });
addNameToFrequencyMap(collidingSymbolMap, originalName, symbol);
}
else {
const identifier = getSynthesizedDeepClone(node);
@@ -202,6 +199,7 @@ namespace ts.codefix {
synthNamesMap.set(symbolIdString, { identifier, types: [], numberOfAssignmentsOriginal: allVarNames.filter(elem => elem.identifier.text === node.text).length/*, numberOfAssignmentsSynthesized: 0*/ });
if ((isParameter(node.parent) && isExpressionOrCallOnTypePromise(node.parent.parent)) || isVariableDeclaration(node.parent)) {
allVarNames.push({ identifier, symbol });
addNameToFrequencyMap(collidingSymbolMap, originalName, symbol);
}
}
}
@@ -229,9 +227,7 @@ namespace ts.codefix {
if (renameInfo) {
const type = checker.getTypeAtLocation(node);
if (type) {
originalType.set(getNodeId(clone).toString(), type);
}
originalType.set(getNodeId(clone).toString(), type);
}
}
@@ -244,17 +240,27 @@ namespace ts.codefix {
}
function getNewNameIfConflict(name: Identifier, allVarNames: SymbolAndIdentifier[]): SynthIdentifier {
const numVarsSameName = allVarNames.filter(elem => elem.identifier.text === name.text).length;
function addNameToFrequencyMap(renamedVarNameFrequencyMap: Map<Symbol[]>, originalName: string, symbol: Symbol) {
if (renamedVarNameFrequencyMap.has(originalName)) {
renamedVarNameFrequencyMap.get(originalName)!.push(symbol);
}
else {
renamedVarNameFrequencyMap.set(originalName, [symbol]);
}
}
function getNewNameIfConflict(name: Identifier, originalNames: ReadonlyMap<Symbol[]>): SynthIdentifier {
const numVarsSameName = (originalNames.get(name.text) || emptyArray).length;
const numberOfAssignmentsOriginal = 0;
const identifier = numVarsSameName === 0 ? name : createIdentifier(name.text + "_" + numVarsSameName);
return { identifier, types: [], numberOfAssignmentsOriginal };
}
// dispatch function to recursively build the refactoring
function transformExpression(node: Expression, transformer: Transformer, outermostParent: CallExpression, prevArgName?: SynthIdentifier): Statement[] {
// should be kept up to date with isFixablePromiseHandler in suggestionDiagnostics.ts
function transformExpression(node: Expression, transformer: Transformer, outermostParent: CallExpression, prevArgName?: SynthIdentifier): ReadonlyArray<Statement> {
if (!node) {
return [];
return emptyArray;
}
const originalType = isIdentifier(node) && transformer.originalTypeMap.get(getNodeId(node).toString());
@@ -273,10 +279,11 @@ namespace ts.codefix {
return transformPromiseCall(node, transformer, prevArgName);
}
return [];
codeActionSucceeded = false;
return emptyArray;
}
function transformCatch(node: CallExpression, transformer: Transformer, prevArgName?: SynthIdentifier): Statement[] {
function transformCatch(node: CallExpression, transformer: Transformer, prevArgName?: SynthIdentifier): ReadonlyArray<Statement> {
const func = node.arguments[0];
const argName = getArgName(func, transformer);
const shouldReturn = transformer.setOfExpressionsToReturn.get(getNodeId(node).toString());
@@ -290,20 +297,21 @@ namespace ts.codefix {
prevArgName.numberOfAssignmentsOriginal = 2; // Try block and catch block
transformer.synthNamesMap.forEach((val, key) => {
if (val.identifier.text === prevArgName.identifier.text) {
transformer.synthNamesMap.set(key, getNewNameIfConflict(prevArgName.identifier, transformer.allVarNames));
const newSynthName = createUniqueSynthName(prevArgName);
transformer.synthNamesMap.set(key, newSynthName);
}
});
// update the constIdentifiers list
if (transformer.constIdentifiers.some(elem => elem.text === prevArgName.identifier.text)) {
transformer.constIdentifiers.push(getNewNameIfConflict(prevArgName.identifier, transformer.allVarNames).identifier);
transformer.constIdentifiers.push(createUniqueSynthName(prevArgName).identifier);
}
}
const tryBlock = createBlock(transformExpression(node.expression, transformer, node, prevArgName));
const transformationBody = getTransformationBody(func, prevArgName, argName, node, transformer);
const catchArg = argName.identifier.text.length > 0 ? argName.identifier.text : "e";
const catchArg = argName ? argName.identifier.text : "e";
const catchClause = createCatchClause(catchArg, createBlock(transformationBody));
/*
@@ -322,7 +330,13 @@ namespace ts.codefix {
return varDeclList ? [varDeclList, tryStatement] : [tryStatement];
}
function transformThen(node: CallExpression, transformer: Transformer, outermostParent: CallExpression, prevArgName?: SynthIdentifier): Statement[] {
function createUniqueSynthName(prevArgName: SynthIdentifier) {
const renamedPrevArg = createOptimisticUniqueName(prevArgName.identifier.text);
const newSynthName = { identifier: renamedPrevArg, types: [], numberOfAssignmentsOriginal: 0 };
return newSynthName;
}
function transformThen(node: CallExpression, transformer: Transformer, outermostParent: CallExpression, prevArgName?: SynthIdentifier): ReadonlyArray<Statement> {
const [res, rej] = node.arguments;
if (!res) {
@@ -339,79 +353,95 @@ namespace ts.codefix {
const transformationBody2 = getTransformationBody(rej, prevArgName, argNameRej, node, transformer);
const catchArg = argNameRej.identifier.text.length > 0 ? argNameRej.identifier.text : "e";
const catchArg = argNameRej ? argNameRej.identifier.text : "e";
const catchClause = createCatchClause(catchArg, createBlock(transformationBody2));
return [createTry(tryBlock, catchClause, /* finallyBlock */ undefined) as Statement];
}
else {
return transformExpression(node.expression, transformer, node, argNameRes).concat(transformationBody);
return [createTry(tryBlock, catchClause, /* finallyBlock */ undefined)];
}
return [];
return transformExpression(node.expression, transformer, node, argNameRes).concat(transformationBody);
}
function getFlagOfIdentifier(node: Identifier, constIdentifiers: Identifier[]): NodeFlags {
function getFlagOfIdentifier(node: Identifier, constIdentifiers: ReadonlyArray<Identifier>): NodeFlags {
const inArr: boolean = constIdentifiers.some(elem => elem.text === node.text);
return inArr ? NodeFlags.Const : NodeFlags.Let;
}
function transformPromiseCall(node: Expression, transformer: Transformer, prevArgName?: SynthIdentifier): Statement[] {
function transformPromiseCall(node: Expression, transformer: Transformer, prevArgName?: SynthIdentifier): ReadonlyArray<Statement> {
const shouldReturn = transformer.setOfExpressionsToReturn.get(getNodeId(node).toString());
// the identifier is empty when the handler (.then()) ignores the argument - In this situation we do not need to save the result of the promise returning call
const hasPrevArgName = prevArgName && prevArgName.identifier.text.length > 0;
const originalNodeParent = node.original ? node.original.parent : node.parent;
if (hasPrevArgName && !shouldReturn && (!originalNodeParent || isPropertyAccessExpression(originalNodeParent))) {
return createVariableDeclarationOrAssignment(prevArgName!, createAwait(node), transformer).concat(); // hack to make the types match
if (prevArgName && !shouldReturn && (!originalNodeParent || isPropertyAccessExpression(originalNodeParent))) {
return createTransformedStatement(prevArgName, createAwait(node), transformer);
}
else if (!hasPrevArgName && !shouldReturn && (!originalNodeParent || isPropertyAccessExpression(originalNodeParent))) {
else if (!prevArgName && !shouldReturn && (!originalNodeParent || isPropertyAccessExpression(originalNodeParent))) {
return [createStatement(createAwait(node))];
}
return [createReturn(getSynthesizedDeepClone(node))];
}
function createVariableDeclarationOrAssignment(prevArgName: SynthIdentifier, rightHandSide: Expression, transformer: Transformer): NodeArray<Statement> {
if (prevArgName.types.length < prevArgName.numberOfAssignmentsOriginal) {
return createNodeArray([createStatement(createAssignment(getSynthesizedDeepClone(prevArgName.identifier), rightHandSide))]);
function createTransformedStatement(prevArgName: SynthIdentifier | undefined, rightHandSide: Expression, transformer: Transformer): ReadonlyArray<Statement> {
if (!prevArgName || prevArgName.identifier.text.length === 0) {
// if there's no argName to assign to, there still might be side effects
return [createStatement(rightHandSide)];
}
return createNodeArray([createVariableStatement(/*modifiers*/ undefined,
(createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier), /*type*/ undefined, rightHandSide)], getFlagOfIdentifier(prevArgName.identifier, transformer.constIdentifiers))))]);
if (prevArgName.types.length < prevArgName.numberOfAssignmentsOriginal) {
// if the variable has already been declared, we don't need "let" or "const"
return [createStatement(createAssignment(getSynthesizedDeepClone(prevArgName.identifier), rightHandSide))];
}
return [createVariableStatement(/*modifiers*/ undefined,
(createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier), /*type*/ undefined, rightHandSide)], getFlagOfIdentifier(prevArgName.identifier, transformer.constIdentifiers))))];
}
function getTransformationBody(func: Node, prevArgName: SynthIdentifier | undefined, argName: SynthIdentifier, parent: CallExpression, transformer: Transformer): NodeArray<Statement> {
// should be kept up to date with isFixablePromiseArgument in suggestionDiagnostics.ts
function getTransformationBody(func: Expression, prevArgName: SynthIdentifier | undefined, argName: SynthIdentifier | undefined, parent: CallExpression, transformer: Transformer): ReadonlyArray<Statement> {
const hasPrevArgName = prevArgName && prevArgName.identifier.text.length > 0;
const hasArgName = argName && argName.identifier.text.length > 0;
const shouldReturn = transformer.setOfExpressionsToReturn.get(getNodeId(parent).toString());
switch (func.kind) {
case SyntaxKind.Identifier:
if (!hasArgName) break;
const synthCall = createCall(getSynthesizedDeepClone(func) as Identifier, /*typeArguments*/ undefined, [argName.identifier]);
if (shouldReturn) {
return createNodeArray([createReturn(synthCall)]);
case SyntaxKind.NullKeyword:
// do not produce a transformed statement for a null argument
break;
case SyntaxKind.Identifier: // identifier includes undefined
if (!argName) {
// undefined was argument passed to promise handler
break;
}
if (!hasPrevArgName) break;
const synthCall = createCall(getSynthesizedDeepClone(func as Identifier), /*typeArguments*/ undefined, [argName.identifier]);
if (shouldReturn) {
return [createReturn(synthCall)];
}
const type = transformer.originalTypeMap.get(getNodeId(func).toString());
const callSignatures = type && transformer.checker.getSignaturesOfType(type, SignatureKind.Call);
const returnType = callSignatures && callSignatures[0].getReturnType();
const varDeclOrAssignment = createVariableDeclarationOrAssignment(prevArgName!, createAwait(synthCall), transformer);
prevArgName!.types.push(returnType!);
const type = transformer.originalTypeMap.get(getNodeId(func).toString()) || transformer.checker.getTypeAtLocation(func);
const callSignatures = transformer.checker.getSignaturesOfType(type, SignatureKind.Call);
if (!callSignatures.length) {
// if identifier in handler has no call signatures, it's invalid
codeActionSucceeded = false;
break;
}
const returnType = callSignatures[0].getReturnType();
const varDeclOrAssignment = createTransformedStatement(prevArgName, createAwait(synthCall), transformer);
if (prevArgName) {
prevArgName.types.push(returnType);
}
return varDeclOrAssignment;
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
case SyntaxKind.ArrowFunction: {
const funcBody = (func as FunctionExpression | ArrowFunction).body;
// Arrow functions with block bodies { } will enter this control flow
if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) {
if (isBlock(funcBody)) {
let refactoredStmts: Statement[] = [];
let seenReturnStatement = false;
for (const statement of funcBody.statements) {
if (isReturnStatement(statement)) {
seenReturnStatement = true;
}
for (const statement of func.body.statements) {
if (getReturnStatementsWithPromiseHandlers(statement).length) {
refactoredStmts = refactoredStmts.concat(getInnerTransformationBody(transformer, [statement], prevArgName));
}
@@ -420,47 +450,64 @@ namespace ts.codefix {
}
}
return shouldReturn ? getSynthesizedDeepClones(createNodeArray(refactoredStmts)) :
removeReturns(createNodeArray(refactoredStmts), prevArgName!.identifier, transformer.constIdentifiers);
return shouldReturn ? refactoredStmts.map(s => getSynthesizedDeepClone(s)) :
removeReturns(
refactoredStmts,
prevArgName === undefined ? undefined : prevArgName.identifier,
transformer,
seenReturnStatement);
}
else {
const funcBody = (<ArrowFunction>func).body;
const innerRetStmts = getReturnStatementsWithPromiseHandlers(createReturn(funcBody as Expression));
const innerRetStmts = getReturnStatementsWithPromiseHandlers(createReturn(funcBody));
const innerCbBody = getInnerTransformationBody(transformer, innerRetStmts, prevArgName);
if (innerCbBody.length > 0) {
return createNodeArray(innerCbBody);
return innerCbBody;
}
if (hasPrevArgName && !shouldReturn) {
const type = transformer.checker.getTypeAtLocation(func);
const returnType = getLastCallSignature(type, transformer.checker).getReturnType();
const varDeclOrAssignment = createVariableDeclarationOrAssignment(prevArgName!, getSynthesizedDeepClone(funcBody) as Expression, transformer);
prevArgName!.types.push(returnType);
return varDeclOrAssignment;
const type = transformer.checker.getTypeAtLocation(func);
const returnType = getLastCallSignature(type, transformer.checker)!.getReturnType();
const rightHandSide = getSynthesizedDeepClone(funcBody);
const possiblyAwaitedRightHandSide = !!transformer.checker.getPromisedTypeOfPromise(returnType) ? createAwait(rightHandSide) : rightHandSide;
if (!shouldReturn) {
const transformedStatement = createTransformedStatement(prevArgName, possiblyAwaitedRightHandSide, transformer);
if (prevArgName) {
prevArgName.types.push(returnType);
}
return transformedStatement;
}
else {
return createNodeArray([createReturn(getSynthesizedDeepClone(funcBody) as Expression)]);
return [createReturn(possiblyAwaitedRightHandSide)];
}
}
}
default:
// If no cases apply, we've found a transformation body we don't know how to handle, so the refactoring should no-op to avoid deleting code.
codeActionSucceeded = false;
break;
}
return createNodeArray([]);
return emptyArray;
}
function getLastCallSignature(type: Type, checker: TypeChecker): Signature {
const callSignatures = type && checker.getSignaturesOfType(type, SignatureKind.Call);
return callSignatures && callSignatures[callSignatures.length - 1];
function getLastCallSignature(type: Type, checker: TypeChecker): Signature | undefined {
const callSignatures = checker.getSignaturesOfType(type, SignatureKind.Call);
return lastOrUndefined(callSignatures);
}
function removeReturns(stmts: NodeArray<Statement>, prevArgName: Identifier, constIdentifiers: Identifier[]): NodeArray<Statement> {
function removeReturns(stmts: ReadonlyArray<Statement>, prevArgName: Identifier | undefined, transformer: Transformer, seenReturnStatement: boolean): ReadonlyArray<Statement> {
const ret: Statement[] = [];
for (const stmt of stmts) {
if (isReturnStatement(stmt)) {
if (stmt.expression) {
ret.push(createVariableStatement(/*modifiers*/ undefined,
(createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, stmt.expression)], getFlagOfIdentifier(prevArgName, constIdentifiers)))));
const possiblyAwaitedExpression = isPromiseReturningExpression(stmt.expression, transformer.checker) ? createAwait(stmt.expression) : stmt.expression;
if (prevArgName === undefined) {
ret.push(createExpressionStatement(possiblyAwaitedExpression));
}
else {
ret.push(createVariableStatement(/*modifiers*/ undefined,
(createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, possiblyAwaitedExpression)], getFlagOfIdentifier(prevArgName, transformer.constIdentifiers)))));
}
}
}
else {
@@ -468,15 +515,21 @@ namespace ts.codefix {
}
}
return createNodeArray(ret);
// if block has no return statement, need to define prevArgName as undefined to prevent undeclared variables
if (!seenReturnStatement && prevArgName !== undefined) {
ret.push(createVariableStatement(/*modifiers*/ undefined,
(createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, createIdentifier("undefined"))], getFlagOfIdentifier(prevArgName, transformer.constIdentifiers)))));
}
return ret;
}
function getInnerTransformationBody(transformer: Transformer, innerRetStmts: Node[], prevArgName?: SynthIdentifier) {
function getInnerTransformationBody(transformer: Transformer, innerRetStmts: ReadonlyArray<Node>, prevArgName?: SynthIdentifier) {
let innerCbBody: Statement[] = [];
for (const stmt of innerRetStmts) {
forEachChild(stmt, function visit(node: Node) {
forEachChild(stmt, function visit(node) {
if (isCallExpression(node)) {
const temp = transformExpression(node, transformer, node, prevArgName);
innerCbBody = innerCbBody.concat(temp);
@@ -492,15 +545,7 @@ namespace ts.codefix {
return innerCbBody;
}
function hasPropertyAccessExpressionWithName(node: CallExpression, funcName: string): boolean {
if (!isPropertyAccessExpression(node.expression)) {
return false;
}
return node.expression.name.text === funcName;
}
function getArgName(funcNode: Node, transformer: Transformer): SynthIdentifier {
function getArgName(funcNode: Expression, transformer: Transformer): SynthIdentifier | undefined {
const numberOfAssignmentsOriginal = 0;
const types: Type[] = [];
@@ -510,23 +555,21 @@ namespace ts.codefix {
if (isFunctionLikeDeclaration(funcNode)) {
if (funcNode.parameters.length > 0) {
const param = funcNode.parameters[0].name as Identifier;
name = getMapEntryIfExists(param);
name = getMapEntryOrDefault(param);
}
}
else if (isCallExpression(funcNode) && funcNode.arguments.length > 0 && isIdentifier(funcNode.arguments[0])) {
name = { identifier: funcNode.arguments[0] as Identifier, types, numberOfAssignmentsOriginal };
}
else if (isIdentifier(funcNode)) {
name = getMapEntryIfExists(funcNode);
name = getMapEntryOrDefault(funcNode);
}
if (!name || name.identifier === undefined || name.identifier.text === "_" || name.identifier.text === "undefined") {
return { identifier: createIdentifier(""), types, numberOfAssignmentsOriginal };
// return undefined argName when arg is null or undefined
if (!name || name.identifier.text === "undefined") {
return undefined;
}
return name;
function getMapEntryIfExists(identifier: Identifier): SynthIdentifier {
function getMapEntryOrDefault(identifier: Identifier): SynthIdentifier {
const originalNode = getOriginalNode(identifier);
const symbol = getSymbol(originalNode);
@@ -546,4 +589,4 @@ namespace ts.codefix {
return node.original ? node.original : node;
}
}
}
}
@@ -12,7 +12,7 @@ namespace ts.codefix {
getCodeActions(context) {
const { sourceFile, program, span, host, formatContext } = context;
if (!isInJavaScriptFile(sourceFile) || !isCheckJsEnabledForFile(sourceFile, program.getCompilerOptions())) {
if (!isInJSFile(sourceFile) || !isCheckJsEnabledForFile(sourceFile, program.getCompilerOptions())) {
return undefined;
}
@@ -20,7 +20,7 @@ namespace ts.codefix {
const { parentDeclaration, declSourceFile, inJs, makeStatic, token, call } = info;
const methodCodeAction = call && getActionForMethodDeclaration(context, declSourceFile, parentDeclaration, token, call, makeStatic, inJs, context.preferences);
const addMember = inJs && !isInterfaceDeclaration(parentDeclaration) ?
singleElementArray(getActionsForAddMissingMemberInJavaScriptFile(context, declSourceFile, parentDeclaration, token.text, makeStatic)) :
singleElementArray(getActionsForAddMissingMemberInJavascriptFile(context, declSourceFile, parentDeclaration, token.text, makeStatic)) :
getActionsForAddMissingMemberInTypeScriptFile(context, declSourceFile, parentDeclaration, token, makeStatic);
return concatenate(singleElementArray(methodCodeAction), addMember);
},
@@ -131,7 +131,7 @@ namespace ts.codefix {
if (classOrInterface) {
const makeStatic = ((leftExpressionType as TypeReference).target || leftExpressionType) !== checker.getDeclaredTypeOfSymbol(symbol);
const declSourceFile = classOrInterface.getSourceFile();
const inJs = isSourceFileJavaScript(declSourceFile);
const inJs = isSourceFileJS(declSourceFile);
const call = tryCast(parent.parent, isCallExpression);
return { kind: InfoKind.ClassOrInterface, token, parentDeclaration: classOrInterface, makeStatic, declSourceFile, inJs, call };
}
@@ -142,7 +142,7 @@ namespace ts.codefix {
return undefined;
}
function getActionsForAddMissingMemberInJavaScriptFile(context: CodeFixContext, declSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean): CodeFixAction | undefined {
function getActionsForAddMissingMemberInJavascriptFile(context: CodeFixContext, declSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean): CodeFixAction | undefined {
const changes = textChanges.ChangeTracker.with(context, t => addMissingMemberInJs(t, declSourceFile, classDeclaration, tokenName, makeStatic));
return changes.length === 0 ? undefined
: createCodeFixAction(fixName, changes, [makeStatic ? Diagnostics.Initialize_static_property_0 : Diagnostics.Initialize_property_0_in_the_constructor, tokenName], fixId, Diagnostics.Add_all_missing_members);
+132 -14
View File
@@ -1,6 +1,9 @@
/* @internal */
namespace ts.codefix {
const fixId = "fixCannotFindModule";
const fixName = "fixCannotFindModule";
const fixIdInstallTypesPackage = "installTypesPackage";
const fixIdGenerateTypes = "generateTypes";
const errorCodeCannotFindModule = Diagnostics.Cannot_find_module_0.code;
const errorCodes = [
errorCodeCannotFindModule,
@@ -10,26 +13,141 @@ namespace ts.codefix {
errorCodes,
getCodeActions: context => {
const { host, sourceFile, span: { start } } = context;
const packageName = getTypesPackageNameToInstall(host, sourceFile, start, context.errorCode);
return packageName === undefined ? []
: [createCodeFixAction(fixId, /*changes*/ [], [Diagnostics.Install_0, packageName], fixId, Diagnostics.Install_all_missing_types_packages, getCommand(sourceFile.fileName, packageName))];
const packageName = tryGetImportedPackageName(sourceFile, start);
if (packageName === undefined) return undefined;
const typesPackageName = getTypesPackageNameToInstall(packageName, host, context.errorCode);
return typesPackageName === undefined
? singleElementArray(tryGetGenerateTypesAction(context, packageName))
: [createCodeFixAction(fixName, /*changes*/ [], [Diagnostics.Install_0, typesPackageName], fixIdInstallTypesPackage, Diagnostics.Install_all_missing_types_packages, getInstallCommand(sourceFile.fileName, typesPackageName))];
},
fixIds: [fixIdInstallTypesPackage, fixIdGenerateTypes],
getAllCodeActions: context => {
let savedTypesDir: string | null | undefined = null; // tslint:disable-line no-null-keyword
return codeFixAll(context, errorCodes, (changes, diag, commands) => {
const packageName = tryGetImportedPackageName(diag.file, diag.start);
if (packageName === undefined) return undefined;
switch (context.fixId) {
case fixIdInstallTypesPackage: {
const pkg = getTypesPackageNameToInstall(packageName, context.host, diag.code);
if (pkg) {
commands.push(getInstallCommand(diag.file.fileName, pkg));
}
break;
}
case fixIdGenerateTypes: {
const typesDir = savedTypesDir !== null ? savedTypesDir : savedTypesDir = getOrCreateTypesDirectory(changes, context);
const command = typesDir === undefined ? undefined : tryGenerateTypes(typesDir, packageName, context);
if (command) commands.push(command);
break;
}
default:
Debug.fail(`Bad fixId: ${context.fixId}`);
}
});
},
fixIds: [fixId],
getAllCodeActions: context => codeFixAll(context, errorCodes, (_, diag, commands) => {
const pkg = getTypesPackageNameToInstall(context.host, diag.file, diag.start, diag.code);
if (pkg) {
commands.push(getCommand(diag.file.fileName, pkg));
}
}),
});
function getCommand(fileName: string, packageName: string): InstallPackageAction {
function tryGetGenerateTypesAction(context: CodeFixContextBase, packageName: string): CodeFixAction | undefined {
let command: GenerateTypesAction | undefined;
const changes = textChanges.ChangeTracker.with(context, t => {
const typesDir = getOrCreateTypesDirectory(t, context);
command = typesDir === undefined ? undefined : tryGenerateTypes(typesDir, packageName, context);
});
return command && createCodeFixAction(fixName, changes, [Diagnostics.Generate_types_for_0, packageName], fixIdGenerateTypes, Diagnostics.Generate_types_for_all_packages_without_types, command);
}
function tryGenerateTypes(typesDir: string, packageName: string, context: CodeFixContextBase): GenerateTypesAction | undefined {
const file = context.sourceFile.fileName;
const fileToGenerateTypesFor = tryResolveJSModule(packageName, getDirectoryPath(file), context.host as ModuleResolutionHost); // TODO: GH#18217
if (fileToGenerateTypesFor === undefined) return undefined;
const outputFileName = resolvePath(getDirectoryPath(context.program.getCompilerOptions().configFile!.fileName), typesDir, packageName + ".d.ts");
if (context.host.fileExists!(outputFileName)) return undefined;
return { type: "generate types", file, fileToGenerateTypesFor, outputFileName };
}
// If no types directory exists yet, adds it to tsconfig.json
function getOrCreateTypesDirectory(changes: textChanges.ChangeTracker, context: CodeFixContextBase): string | undefined {
const { configFile } = context.program.getCompilerOptions();
if (!configFile) return undefined;
const tsconfigObjectLiteral = getTsConfigObjectLiteralExpression(configFile);
if (!tsconfigObjectLiteral) return undefined;
const compilerOptionsProperty = findProperty(tsconfigObjectLiteral, "compilerOptions");
if (!compilerOptionsProperty) {
const newCompilerOptions = createObjectLiteral([makeDefaultBaseUrl(), makeDefaultPaths()]);
changes.insertNodeAtObjectStart(configFile, tsconfigObjectLiteral, createJsonPropertyAssignment("compilerOptions", newCompilerOptions));
return defaultTypesDirectoryName;
}
const compilerOptions = compilerOptionsProperty.initializer;
if (!isObjectLiteralExpression(compilerOptions)) return defaultTypesDirectoryName;
const baseUrl = getOrAddBaseUrl(changes, configFile, compilerOptions);
const typesDirectoryFromPathMapping = getOrAddPathMapping(changes, configFile, compilerOptions);
return combinePaths(baseUrl, typesDirectoryFromPathMapping);
}
const defaultBaseUrl = ".";
function makeDefaultBaseUrl(): PropertyAssignment {
return createJsonPropertyAssignment("baseUrl", createStringLiteral(defaultBaseUrl));
}
function getOrAddBaseUrl(changes: textChanges.ChangeTracker, tsconfig: TsConfigSourceFile, compilerOptions: ObjectLiteralExpression): string {
const baseUrlProp = findProperty(compilerOptions, "baseUrl");
if (baseUrlProp) {
return isStringLiteral(baseUrlProp.initializer) ? baseUrlProp.initializer.text : defaultBaseUrl;
}
else {
changes.insertNodeAtObjectStart(tsconfig, compilerOptions, makeDefaultBaseUrl());
return defaultBaseUrl;
}
}
const defaultTypesDirectoryName = "types";
function makeDefaultPathMapping(): PropertyAssignment {
return createJsonPropertyAssignment("*", createArrayLiteral([createStringLiteral(`${defaultTypesDirectoryName}/*`)]));
}
function makeDefaultPaths(): PropertyAssignment {
return createJsonPropertyAssignment("paths", createObjectLiteral([makeDefaultPathMapping()]));
}
function getOrAddPathMapping(changes: textChanges.ChangeTracker, tsconfig: TsConfigSourceFile, compilerOptions: ObjectLiteralExpression) {
const paths = findProperty(compilerOptions, "paths");
if (!paths || !isObjectLiteralExpression(paths.initializer)) {
changes.insertNodeAtObjectStart(tsconfig, compilerOptions, makeDefaultPaths());
return defaultTypesDirectoryName;
}
// Look for an existing path mapping. Should look like `"*": "foo/*"`.
const existing = firstDefined(paths.initializer.properties, prop =>
isPropertyAssignment(prop) && isStringLiteral(prop.name) && prop.name.text === "*" && isArrayLiteralExpression(prop.initializer)
? firstDefined(prop.initializer.elements, value => isStringLiteral(value) ? tryRemoveSuffix(value.text, "/*") : undefined)
: undefined);
if (existing) return existing;
changes.insertNodeAtObjectStart(tsconfig, paths.initializer, makeDefaultPathMapping());
return defaultTypesDirectoryName;
}
function createJsonPropertyAssignment(name: string, initializer: Expression) {
return createPropertyAssignment(createStringLiteral(name), initializer);
}
function findProperty(obj: ObjectLiteralExpression, name: string): PropertyAssignment | undefined {
return find(obj.properties, (p): p is PropertyAssignment => isPropertyAssignment(p) && !!p.name && isStringLiteral(p.name) && p.name.text === name);
}
function getInstallCommand(fileName: string, packageName: string): InstallPackageAction {
return { type: "install package", file: fileName, packageName };
}
function getTypesPackageNameToInstall(host: LanguageServiceHost, sourceFile: SourceFile, pos: number, diagCode: number): string | undefined {
function tryGetImportedPackageName(sourceFile: SourceFile, pos: number): string | undefined {
const moduleName = cast(getTokenAtPosition(sourceFile, pos), isStringLiteral).text;
const { packageName } = getPackageName(moduleName);
const { packageName } = parsePackageName(moduleName);
return isExternalModuleNameRelative(packageName) ? undefined : packageName;
}
function getTypesPackageNameToInstall(packageName: string, host: LanguageServiceHost, diagCode: number): string | undefined {
return diagCode === errorCodeCannotFindModule
? (JsTyping.nodeCoreModules.has(packageName) ? "@types/node" : undefined)
: (host.isKnownTypesPackageName!(packageName) ? getTypesPackageName(packageName) : undefined); // TODO: GH#18217
@@ -110,7 +110,7 @@ namespace ts.codefix {
function getDefaultValueFromType (checker: TypeChecker, type: Type): Expression | undefined {
if (type.flags & TypeFlags.BooleanLiteral) {
return type === checker.getFalseType() ? createFalse() : createTrue();
return (type === checker.getFalseType() || type === checker.getFalseType(/*fresh*/ true)) ? createFalse() : createTrue();
}
else if (type.isLiteral()) {
return createLiteral(type.value);
+227
View File
@@ -0,0 +1,227 @@
/* @internal */
namespace ts {
export function generateTypesForModule(name: string, moduleValue: unknown, formatSettings: FormatCodeSettings): string {
return valueInfoToDeclarationFileText(inspectValue(name, moduleValue), formatSettings);
}
export function valueInfoToDeclarationFileText(valueInfo: ValueInfo, formatSettings: FormatCodeSettings): string {
return textChanges.getNewFileText(toStatements(valueInfo, OutputKind.ExportEquals), ScriptKind.TS, "\n", formatting.getFormatContext(formatSettings));
}
const enum OutputKind { ExportEquals, NamedExport, NamespaceMember }
function toNamespaceMemberStatements(info: ValueInfo): ReadonlyArray<Statement> {
return toStatements(info, OutputKind.NamespaceMember);
}
function toStatements(info: ValueInfo, kind: OutputKind): ReadonlyArray<Statement> {
const isDefault = info.name === InternalSymbolName.Default;
const name = isDefault ? "_default" : info.name;
if (!isValidIdentifier(name) || isDefault && kind !== OutputKind.NamedExport) return emptyArray;
const modifiers = isDefault && info.kind === ValueKind.FunctionOrClass ? [createModifier(SyntaxKind.ExportKeyword), createModifier(SyntaxKind.DefaultKeyword)]
: kind === OutputKind.ExportEquals ? [createModifier(SyntaxKind.DeclareKeyword)]
: kind === OutputKind.NamedExport ? [createModifier(SyntaxKind.ExportKeyword)]
: undefined;
const exportEquals = () => kind === OutputKind.ExportEquals ? [exportEqualsOrDefault(info.name, /*isExportEquals*/ true)] : emptyArray;
const exportDefault = () => isDefault ? [exportEqualsOrDefault("_default", /*isExportEquals*/ false)] : emptyArray;
switch (info.kind) {
case ValueKind.FunctionOrClass:
return [...exportEquals(), ...functionOrClassToStatements(modifiers, name, info)];
case ValueKind.Object:
const { members } = info;
if (kind === OutputKind.ExportEquals) {
return flatMap(members, v => toStatements(v, OutputKind.NamedExport));
}
if (members.some(m => m.kind === ValueKind.FunctionOrClass)) {
// If some member is a function, use a namespace so it gets a FunctionDeclaration or ClassDeclaration.
return [...exportDefault(), createNamespace(modifiers, name, flatMap(members, toNamespaceMemberStatements))];
}
// falls through
case ValueKind.Const:
case ValueKind.Array: {
const comment = info.kind === ValueKind.Const ? info.comment : undefined;
const constVar = createVariableStatement(modifiers, createVariableDeclarationList([createVariableDeclaration(name, toType(info))], NodeFlags.Const));
return [...exportEquals(), ...exportDefault(), addComment(constVar, comment)];
}
default:
return Debug.assertNever(info);
}
}
function exportEqualsOrDefault(name: string, isExportEquals: boolean): ExportAssignment {
return createExportAssignment(/*decorators*/ undefined, /*modifiers*/ undefined, isExportEquals, createIdentifier(name));
}
function functionOrClassToStatements(modifiers: Modifiers, name: string, { source, prototypeMembers, namespaceMembers }: ValueInfoFunctionOrClass): ReadonlyArray<Statement> {
const fnAst = parseClassOrFunctionBody(source);
const { parameters, returnType } = fnAst === undefined ? { parameters: emptyArray, returnType: anyType() } : getParametersAndReturnType(fnAst);
const instanceProperties = typeof fnAst === "object" ? getConstructorFunctionInstanceProperties(fnAst) : emptyArray;
const classStaticMembers: ClassElement[] | undefined =
instanceProperties.length !== 0 || prototypeMembers.length !== 0 || fnAst === undefined || typeof fnAst !== "number" && fnAst.kind === SyntaxKind.Constructor ? [] : undefined;
const namespaceStatements = flatMap(namespaceMembers, info => {
if (!isValidIdentifier(info.name)) return undefined;
if (classStaticMembers) {
switch (info.kind) {
case ValueKind.Object:
if (info.members.some(m => m.kind === ValueKind.FunctionOrClass)) {
break;
}
// falls through
case ValueKind.Array:
case ValueKind.Const:
classStaticMembers.push(
addComment(
createProperty(/*decorators*/ undefined, [createModifier(SyntaxKind.StaticKeyword)], info.name, /*questionOrExclamationToken*/ undefined, toType(info), /*initializer*/ undefined),
info.kind === ValueKind.Const ? info.comment : undefined));
return undefined;
case ValueKind.FunctionOrClass:
if (!info.namespaceMembers.length) { // Else, can't merge a static method with a namespace. Must make it a function on the namespace.
const sig = tryGetMethod(info, [createModifier(SyntaxKind.StaticKeyword)]);
if (sig) {
classStaticMembers.push(sig);
return undefined;
}
}
}
}
return toStatements(info, OutputKind.NamespaceMember);
});
const decl = classStaticMembers
? createClassDeclaration(
/*decorators*/ undefined,
modifiers,
name,
/*typeParameters*/ undefined,
/*heritageClauses*/ undefined,
[
...classStaticMembers,
...(parameters.length ? [createConstructor(/*decorators*/ undefined, /*modifiers*/ undefined, parameters, /*body*/ undefined)] : emptyArray),
...instanceProperties,
// ignore non-functions on the prototype
...mapDefined(prototypeMembers, info => info.kind === ValueKind.FunctionOrClass ? tryGetMethod(info) : undefined),
])
: createFunctionDeclaration(/*decorators*/ undefined, modifiers, /*asteriskToken*/ undefined, name, /*typeParameters*/ undefined, parameters, returnType, /*body*/ undefined);
return [decl, ...(namespaceStatements.length === 0 ? emptyArray : [createNamespace(modifiers && modifiers.map(m => getSynthesizedDeepClone(m)), name, namespaceStatements)])];
}
function tryGetMethod({ name, source }: ValueInfoFunctionOrClass, modifiers?: Modifiers): MethodDeclaration | undefined {
if (!isValidIdentifier(name)) return undefined;
const fnAst = parseClassOrFunctionBody(source);
if (fnAst === undefined || (typeof fnAst !== "number" && fnAst.kind === SyntaxKind.Constructor)) return undefined;
const sig = getParametersAndReturnType(fnAst);
return sig && createMethod(
/*decorators*/ undefined,
modifiers,
/*asteriskToken*/ undefined,
name,
/*questionToken*/ undefined,
/*typeParameters*/ undefined,
sig.parameters,
sig.returnType,
/*body*/ undefined);
}
function toType(info: ValueInfo): TypeNode {
switch (info.kind) {
case ValueKind.Const:
return createTypeReferenceNode(info.typeName, /*typeArguments*/ undefined);
case ValueKind.Array:
return createArrayTypeNode(toType(info.inner));
case ValueKind.FunctionOrClass:
return createTypeReferenceNode("Function", /*typeArguments*/ undefined); // Normally we create a FunctionDeclaration, but this can happen for a function in an array.
case ValueKind.Object:
return createTypeLiteralNode(info.members.map(m => createPropertySignature(/*modifiers*/ undefined, m.name, /*questionToken*/ undefined, toType(m), /*initializer*/ undefined)));
default:
return Debug.assertNever(info);
}
}
// Parses assignments to "this.x" in the constructor into class property declarations
function getConstructorFunctionInstanceProperties(fnAst: FunctionOrConstructorNode): ReadonlyArray<PropertyDeclaration> {
const members: PropertyDeclaration[] = [];
forEachOwnNodeOfFunction(fnAst, node => {
if (isAssignmentExpression(node, /*excludeCompoundAssignment*/ true) &&
isPropertyAccessExpression(node.left) && node.left.expression.kind === SyntaxKind.ThisKeyword) {
const name = node.left.name.text;
if (!isJsPrivate(name)) members.push(createProperty(/*decorators*/ undefined, /*modifiers*/ undefined, name, /*questionOrExclamationToken*/ undefined, anyType(), /*initializer*/ undefined));
}
});
return members;
}
interface ParametersAndReturnType { readonly parameters: ReadonlyArray<ParameterDeclaration>; readonly returnType: TypeNode; }
function getParametersAndReturnType(fnAst: FunctionOrConstructor): ParametersAndReturnType {
if (typeof fnAst === "number") {
return { parameters: fill(fnAst, i => makeParameter(`p${i}`, anyType())), returnType: anyType() };
}
let usedArguments = false, hasReturn = false;
forEachOwnNodeOfFunction(fnAst, node => {
usedArguments = usedArguments || isIdentifier(node) && node.text === "arguments";
hasReturn = hasReturn || isReturnStatement(node) && !!node.expression && node.expression.kind !== SyntaxKind.VoidExpression;
});
const parameters = [
...fnAst.parameters.map(p => makeParameter(`${p.name.getText()}`, inferParameterType(fnAst, p))),
...(usedArguments ? [makeRestParameter()] : emptyArray),
];
return { parameters, returnType: hasReturn ? anyType() : createKeywordTypeNode(SyntaxKind.VoidKeyword) };
}
function makeParameter(name: string, type: TypeNode): ParameterDeclaration {
return createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, name, /*questionToken*/ undefined, type);
}
function makeRestParameter(): ParameterDeclaration {
return createParameter(/*decorators*/ undefined, /*modifiers*/ undefined, createToken(SyntaxKind.DotDotDotToken), "args", /*questionToken*/ undefined, createArrayTypeNode(anyType()));
}
type FunctionOrConstructorNode = FunctionExpression | ArrowFunction | ConstructorDeclaration | MethodDeclaration;
type FunctionOrConstructor = FunctionOrConstructorNode | number; // number is for native function
/** Returns 'undefined' for class with no declared constructor */
function parseClassOrFunctionBody(source: string | number): FunctionOrConstructor | undefined {
if (typeof source === "number") return source;
const classOrFunction = tryCast(parseExpression(source), (node): node is FunctionExpression | ArrowFunction | ClassExpression => isFunctionExpression(node) || isArrowFunction(node) || isClassExpression(node));
return classOrFunction
? isClassExpression(classOrFunction) ? find(classOrFunction.members, isConstructorDeclaration) : classOrFunction
// If that didn't parse, it's a method `m() {}`. Parse again inside of an object literal.
: cast(first(cast(parseExpression(`{ ${source} }`), isObjectLiteralExpression).properties), isMethodDeclaration);
}
function parseExpression(expr: string): Expression {
const text = `const _ = ${expr}`;
const srcFile = createSourceFile("test.ts", text, ScriptTarget.Latest, /*setParentNodes*/ true);
return first(cast(first(srcFile.statements), isVariableStatement).declarationList.declarations).initializer!;
}
function inferParameterType(_fn: FunctionOrConstructor, _param: ParameterDeclaration): TypeNode {
// TODO: Inspect function body for clues (see inferFromUsage.ts)
return anyType();
}
// Descends through all nodes in a function, but not in nested functions.
function forEachOwnNodeOfFunction(fnAst: FunctionOrConstructorNode, cb: (node: Node) => void) {
fnAst.body!.forEachChild(function recur(node) {
cb(node);
if (!isFunctionLike(node)) node.forEachChild(recur);
});
}
function isValidIdentifier(name: string): boolean {
const keyword = stringToToken(name);
return !(keyword && isNonContextualKeyword(keyword)) && isIdentifierText(name, ScriptTarget.ESNext);
}
type Modifiers = ReadonlyArray<Modifier> | undefined;
function addComment<T extends Node>(node: T, comment: string | undefined): T {
if (comment !== undefined) addSyntheticLeadingComment(node, SyntaxKind.SingleLineCommentTrivia, comment);
return node;
}
function anyType(): KeywordTypeNode {
return createKeywordTypeNode(SyntaxKind.AnyKeyword);
}
function createNamespace(modifiers: Modifiers, name: string, statements: ReadonlyArray<Statement>): NamespaceDeclaration {
return createModuleDeclaration(/*decorators*/ undefined, modifiers, createIdentifier(name), createModuleBlock(statements), NodeFlags.Namespace) as NamespaceDeclaration;
}
}
+8 -8
View File
@@ -185,20 +185,20 @@ namespace ts.codefix {
const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker, compilerOptions);
if (defaultInfo && defaultInfo.name === symbolName && skipAlias(defaultInfo.symbol, checker) === exportedSymbol) {
result.push({ moduleSymbol, importKind: defaultInfo.kind, exportedSymbolIsTypeOnly: isTypeOnlySymbol(defaultInfo.symbol) });
result.push({ moduleSymbol, importKind: defaultInfo.kind, exportedSymbolIsTypeOnly: isTypeOnlySymbol(defaultInfo.symbol, checker) });
}
for (const exported of checker.getExportsOfModule(moduleSymbol)) {
if (exported.name === symbolName && skipAlias(exported, checker) === exportedSymbol) {
result.push({ moduleSymbol, importKind: ImportKind.Named, exportedSymbolIsTypeOnly: isTypeOnlySymbol(exported) });
result.push({ moduleSymbol, importKind: ImportKind.Named, exportedSymbolIsTypeOnly: isTypeOnlySymbol(exported, checker) });
}
}
});
return result;
}
function isTypeOnlySymbol(s: Symbol): boolean {
return !(s.flags & SymbolFlags.Value);
function isTypeOnlySymbol(s: Symbol, checker: TypeChecker): boolean {
return !(skipAlias(s, checker).flags & SymbolFlags.Value);
}
function getFixForImport(
@@ -267,7 +267,7 @@ namespace ts.codefix {
function getExistingImportDeclarations({ moduleSymbol, importKind, exportedSymbolIsTypeOnly }: SymbolExportInfo, checker: TypeChecker, sourceFile: SourceFile): ReadonlyArray<FixAddToExistingImportInfo> {
// Can't use an es6 import for a type in JS.
return exportedSymbolIsTypeOnly && isSourceFileJavaScript(sourceFile) ? emptyArray : mapDefined<StringLiteralLike, FixAddToExistingImportInfo>(sourceFile.imports, moduleSpecifier => {
return exportedSymbolIsTypeOnly && isSourceFileJS(sourceFile) ? emptyArray : mapDefined<StringLiteralLike, FixAddToExistingImportInfo>(sourceFile.imports, moduleSpecifier => {
const i = importFromModuleSpecifier(moduleSpecifier);
return (i.kind === SyntaxKind.ImportDeclaration || i.kind === SyntaxKind.ImportEqualsDeclaration)
&& checker.getSymbolAtLocation(moduleSpecifier) === moduleSymbol ? { declaration: i, importKind } : undefined;
@@ -282,7 +282,7 @@ namespace ts.codefix {
host: LanguageServiceHost,
preferences: UserPreferences,
): ReadonlyArray<FixAddNewImport | FixUseImportType> {
const isJs = isSourceFileJavaScript(sourceFile);
const isJs = isSourceFileJS(sourceFile);
const choicesForEachExportingModule = flatMap(moduleSymbols, ({ moduleSymbol, importKind, exportedSymbolIsTypeOnly }) =>
moduleSpecifiers.getModuleSpecifiers(moduleSymbol, program.getCompilerOptions(), sourceFile, host, program.getSourceFiles(), preferences, program.redirectTargetsMap)
.map((moduleSpecifier): FixAddNewImport | FixUseImportType =>
@@ -398,7 +398,7 @@ namespace ts.codefix {
// Maps symbol id to info for modules providing that symbol (original export + re-exports).
const originalSymbolToExportInfos = createMultiMap<SymbolExportInfo>();
function addSymbol(moduleSymbol: Symbol, exportedSymbol: Symbol, importKind: ImportKind): void {
originalSymbolToExportInfos.add(getUniqueSymbolId(exportedSymbol, checker).toString(), { moduleSymbol, importKind, exportedSymbolIsTypeOnly: isTypeOnlySymbol(exportedSymbol) });
originalSymbolToExportInfos.add(getUniqueSymbolId(exportedSymbol, checker).toString(), { moduleSymbol, importKind, exportedSymbolIsTypeOnly: isTypeOnlySymbol(exportedSymbol, checker) });
}
forEachExternalModuleToImportFrom(checker, sourceFile, program.getSourceFiles(), moduleSymbol => {
cancellationToken.throwIfCancellationRequested();
@@ -424,7 +424,7 @@ namespace ts.codefix {
if (!exported) return undefined;
const { symbol, kind } = exported;
const info = getDefaultExportInfoWorker(symbol, moduleSymbol, checker, compilerOptions);
return info && { symbol, symbolForMeaning: info.symbolForMeaning, name: info.name, kind };
return info && { symbol, kind, ...info };
}
function getDefaultLikeExportWorker(moduleSymbol: Symbol, checker: TypeChecker): { readonly symbol: Symbol, readonly kind: ImportKind.Default | ImportKind.Equals } | undefined {
+96 -43
View File
@@ -25,24 +25,21 @@ namespace ts.codefix {
registerCodeFix({
errorCodes,
getCodeActions(context) {
const { sourceFile, program, span: { start }, errorCode, cancellationToken } = context;
if (isSourceFileJavaScript(sourceFile)) {
return undefined; // TODO: GH#20113
}
const { sourceFile, program, span: { start }, errorCode, cancellationToken, host } = context;
const token = getTokenAtPosition(sourceFile, start);
let declaration!: Declaration | undefined;
const changes = textChanges.ChangeTracker.with(context, changes => { declaration = doChange(changes, sourceFile, token, errorCode, program, cancellationToken, /*markSeenseen*/ returnTrue); });
const changes = textChanges.ChangeTracker.with(context, changes => { declaration = doChange(changes, sourceFile, token, errorCode, program, cancellationToken, /*markSeen*/ returnTrue, host); });
const name = declaration && getNameOfDeclaration(declaration);
return !name || changes.length === 0 ? undefined
: [createCodeFixAction(fixId, changes, [getDiagnostic(errorCode, token), name.getText(sourceFile)], fixId, Diagnostics.Infer_all_types_from_usage)];
},
fixIds: [fixId],
getAllCodeActions(context) {
const { sourceFile, program, cancellationToken } = context;
const { sourceFile, program, cancellationToken, host } = context;
const markSeen = nodeSeenTracker();
return codeFixAll(context, errorCodes, (changes, err) => {
doChange(changes, sourceFile, getTokenAtPosition(err.file, err.start), err.code, program, cancellationToken, markSeen);
doChange(changes, sourceFile, getTokenAtPosition(err.file, err.start), err.code, program, cancellationToken, markSeen, host);
});
},
});
@@ -50,7 +47,7 @@ namespace ts.codefix {
function getDiagnostic(errorCode: number, token: Node): DiagnosticMessage {
switch (errorCode) {
case Diagnostics.Parameter_0_implicitly_has_an_1_type.code:
return isSetAccessor(getContainingFunction(token)!) ? Diagnostics.Infer_type_of_0_from_usage : Diagnostics.Infer_parameter_types_from_usage; // TODO: GH#18217
return isSetAccessorDeclaration(getContainingFunction(token)!) ? Diagnostics.Infer_type_of_0_from_usage : Diagnostics.Infer_parameter_types_from_usage; // TODO: GH#18217
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code:
return Diagnostics.Infer_parameter_types_from_usage;
default:
@@ -58,8 +55,8 @@ namespace ts.codefix {
}
}
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node, errorCode: number, program: Program, cancellationToken: CancellationToken, markSeen: NodeSeenTracker): Declaration | undefined {
if (!isParameterPropertyModifier(token.kind) && token.kind !== SyntaxKind.Identifier && token.kind !== SyntaxKind.DotDotDotToken) {
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node, errorCode: number, program: Program, cancellationToken: CancellationToken, markSeen: NodeSeenTracker, host: LanguageServiceHost): Declaration | undefined {
if (!isParameterPropertyModifier(token.kind) && token.kind !== SyntaxKind.Identifier && token.kind !== SyntaxKind.DotDotDotToken && token.kind !== SyntaxKind.ThisKeyword) {
return undefined;
}
@@ -69,7 +66,15 @@ namespace ts.codefix {
case Diagnostics.Member_0_implicitly_has_an_1_type.code:
case Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code:
if ((isVariableDeclaration(parent) && markSeen(parent)) || isPropertyDeclaration(parent) || isPropertySignature(parent)) { // handle bad location
annotateVariableDeclaration(changes, sourceFile, parent, program, cancellationToken);
annotateVariableDeclaration(changes, sourceFile, parent, program, host, cancellationToken);
return parent;
}
if (isPropertyAccessExpression(parent)) {
const type = inferTypeForVariableFromUsage(parent.name, program, cancellationToken);
const typeNode = type && getTypeNodeIfAccessible(type, parent, program, host);
if (typeNode) {
changes.tryInsertJSDocType(sourceFile, parent, typeNode);
}
return parent;
}
return undefined;
@@ -77,7 +82,7 @@ namespace ts.codefix {
case Diagnostics.Variable_0_implicitly_has_an_1_type.code: {
const symbol = program.getTypeChecker().getSymbolAtLocation(token);
if (symbol && symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration) && markSeen(symbol.valueDeclaration)) {
annotateVariableDeclaration(changes, sourceFile, symbol.valueDeclaration, program, cancellationToken);
annotateVariableDeclaration(changes, sourceFile, symbol.valueDeclaration, program, host, cancellationToken);
return symbol.valueDeclaration;
}
return undefined;
@@ -92,15 +97,15 @@ namespace ts.codefix {
switch (errorCode) {
// Parameter declarations
case Diagnostics.Parameter_0_implicitly_has_an_1_type.code:
if (isSetAccessor(containingFunction)) {
annotateSetAccessor(changes, sourceFile, containingFunction, program, cancellationToken);
if (isSetAccessorDeclaration(containingFunction)) {
annotateSetAccessor(changes, sourceFile, containingFunction, program, host, cancellationToken);
return containingFunction;
}
// falls through
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code:
if (markSeen(containingFunction)) {
const param = cast(parent, isParameter);
annotateParameters(changes, param, containingFunction, sourceFile, program, cancellationToken);
annotateParameters(changes, param, containingFunction, sourceFile, program, host, cancellationToken);
return param;
}
return undefined;
@@ -108,16 +113,16 @@ namespace ts.codefix {
// Get Accessor declarations
case Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation.code:
case Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type.code:
if (isGetAccessor(containingFunction) && isIdentifier(containingFunction.name)) {
annotate(changes, sourceFile, containingFunction, inferTypeForVariableFromUsage(containingFunction.name, program, cancellationToken), program);
if (isGetAccessorDeclaration(containingFunction) && isIdentifier(containingFunction.name)) {
annotate(changes, sourceFile, containingFunction, inferTypeForVariableFromUsage(containingFunction.name, program, cancellationToken), program, host);
return containingFunction;
}
return undefined;
// Set Accessor declarations
case Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code:
if (isSetAccessor(containingFunction)) {
annotateSetAccessor(changes, sourceFile, containingFunction, program, cancellationToken);
if (isSetAccessorDeclaration(containingFunction)) {
annotateSetAccessor(changes, sourceFile, containingFunction, program, host, cancellationToken);
return containingFunction;
}
return undefined;
@@ -127,9 +132,9 @@ namespace ts.codefix {
}
}
function annotateVariableDeclaration(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: VariableDeclaration | PropertyDeclaration | PropertySignature, program: Program, cancellationToken: CancellationToken): void {
function annotateVariableDeclaration(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: VariableDeclaration | PropertyDeclaration | PropertySignature, program: Program, host: LanguageServiceHost, cancellationToken: CancellationToken): void {
if (isIdentifier(declaration.name)) {
annotate(changes, sourceFile, declaration, inferTypeForVariableFromUsage(declaration.name, program, cancellationToken), program);
annotate(changes, sourceFile, declaration, inferTypeForVariableFromUsage(declaration.name, program, cancellationToken), program, host);
}
}
@@ -145,40 +150,67 @@ namespace ts.codefix {
return false;
}
function annotateParameters(changes: textChanges.ChangeTracker, parameterDeclaration: ParameterDeclaration, containingFunction: FunctionLike, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): void {
function annotateParameters(changes: textChanges.ChangeTracker, parameterDeclaration: ParameterDeclaration, containingFunction: FunctionLike, sourceFile: SourceFile, program: Program, host: LanguageServiceHost, cancellationToken: CancellationToken): void {
if (!isIdentifier(parameterDeclaration.name) || !isApplicableFunctionForInference(containingFunction)) {
return;
}
const types = inferTypeForParametersFromUsage(containingFunction, sourceFile, program, cancellationToken) ||
containingFunction.parameters.map(p => isIdentifier(p.name) ? inferTypeForVariableFromUsage(p.name, program, cancellationToken) : undefined);
// We didn't actually find a set of type inference positions matching each parameter position
if (!types || containingFunction.parameters.length !== types.length) {
return;
}
const parameterInferences = inferTypeForParametersFromUsage(containingFunction, sourceFile, program, cancellationToken) ||
containingFunction.parameters.map<ParameterInference>(p => ({
declaration: p,
type: isIdentifier(p.name) ? inferTypeForVariableFromUsage(p.name, program, cancellationToken) : undefined
}));
Debug.assert(containingFunction.parameters.length === parameterInferences.length);
zipWith(containingFunction.parameters, types, (parameter, type) => {
if (!parameter.type && !parameter.initializer) {
annotate(changes, sourceFile, parameter, type, program);
if (isInJSFile(containingFunction)) {
annotateJSDocParameters(changes, sourceFile, parameterInferences, program, host);
}
else {
for (const { declaration, type } of parameterInferences) {
if (declaration && !declaration.type && !declaration.initializer) {
annotate(changes, sourceFile, declaration, type, program, host);
}
}
});
}
}
function annotateSetAccessor(changes: textChanges.ChangeTracker, sourceFile: SourceFile, setAccessorDeclaration: SetAccessorDeclaration, program: Program, cancellationToken: CancellationToken): void {
function annotateSetAccessor(changes: textChanges.ChangeTracker, sourceFile: SourceFile, setAccessorDeclaration: SetAccessorDeclaration, program: Program, host: LanguageServiceHost, cancellationToken: CancellationToken): void {
const param = firstOrUndefined(setAccessorDeclaration.parameters);
if (param && isIdentifier(setAccessorDeclaration.name) && isIdentifier(param.name)) {
const type = inferTypeForVariableFromUsage(setAccessorDeclaration.name, program, cancellationToken) ||
inferTypeForVariableFromUsage(param.name, program, cancellationToken);
annotate(changes, sourceFile, param, type, program);
if (isInJSFile(setAccessorDeclaration)) {
annotateJSDocParameters(changes, sourceFile, [{ declaration: param, type }], program, host);
}
else {
annotate(changes, sourceFile, param, type, program, host);
}
}
}
function annotate(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: textChanges.TypeAnnotatable, type: Type | undefined, program: Program): void {
const typeNode = type && getTypeNodeIfAccessible(type, declaration, program.getTypeChecker());
if (typeNode) changes.tryInsertTypeAnnotation(sourceFile, declaration, typeNode);
function annotate(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: textChanges.TypeAnnotatable, type: Type | undefined, program: Program, host: LanguageServiceHost): void {
const typeNode = type && getTypeNodeIfAccessible(type, declaration, program, host);
if (typeNode) {
if (isInJSFile(sourceFile) && declaration.kind !== SyntaxKind.PropertySignature) {
changes.tryInsertJSDocType(sourceFile, declaration, typeNode);
}
else {
changes.tryInsertTypeAnnotation(sourceFile, declaration, typeNode);
}
}
}
function getTypeNodeIfAccessible(type: Type, enclosingScope: Node, checker: TypeChecker): TypeNode | undefined {
function annotateJSDocParameters(changes: textChanges.ChangeTracker, sourceFile: SourceFile, parameterInferences: ParameterInference[], program: Program, host: LanguageServiceHost): void {
const result = mapDefined(parameterInferences, inference => {
const param = inference.declaration;
const typeNode = inference.type && getTypeNodeIfAccessible(inference.type, param, program, host);
return typeNode && !param.initializer && !getJSDocType(param) ? { ...inference, typeNode } : undefined;
});
changes.tryInsertJSDocParameters(sourceFile, result);
}
function getTypeNodeIfAccessible(type: Type, enclosingScope: Node, program: Program, host: LanguageServiceHost): TypeNode | undefined {
const checker = program.getTypeChecker();
let typeIsAccessible = true;
const notAccessible = () => { typeIsAccessible = false; };
const res = checker.typeToTypeNode(type, enclosingScope, /*flags*/ undefined, {
@@ -189,6 +221,14 @@ namespace ts.codefix {
reportInaccessibleThisError: notAccessible,
reportPrivateInBaseOfClassExpression: notAccessible,
reportInaccessibleUniqueSymbolError: notAccessible,
moduleResolverHost: {
readFile: host.readFile,
fileExists: host.fileExists,
directoryExists: host.directoryExists,
getSourceFiles: program.getSourceFiles,
getCurrentDirectory: program.getCurrentDirectory,
getCommonSourceDirectory: program.getCommonSourceDirectory,
}
});
return typeIsAccessible ? res : undefined;
}
@@ -196,14 +236,14 @@ namespace ts.codefix {
function getReferences(token: PropertyName | Token<SyntaxKind.ConstructorKeyword>, program: Program, cancellationToken: CancellationToken): ReadonlyArray<Identifier> {
// Position shouldn't matter since token is not a SourceFile.
return mapDefined(FindAllReferences.getReferenceEntriesForNode(-1, token, program, program.getSourceFiles(), cancellationToken), entry =>
entry.type === "node" ? tryCast(entry.node, isIdentifier) : undefined);
entry.kind !== FindAllReferences.EntryKind.Span ? tryCast(entry.node, isIdentifier) : undefined);
}
function inferTypeForVariableFromUsage(token: Identifier, program: Program, cancellationToken: CancellationToken): Type | undefined {
return InferFromReference.inferTypeFromReferences(getReferences(token, program, cancellationToken), program.getTypeChecker(), cancellationToken);
}
function inferTypeForParametersFromUsage(containingFunction: FunctionLikeDeclaration, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): (Type | undefined)[] | undefined {
function inferTypeForParametersFromUsage(containingFunction: FunctionLikeDeclaration, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): ParameterInference[] | undefined {
switch (containingFunction.kind) {
case SyntaxKind.Constructor:
case SyntaxKind.FunctionExpression:
@@ -219,6 +259,13 @@ namespace ts.codefix {
}
}
interface ParameterInference {
declaration: ParameterDeclaration;
type?: Type;
typeNode?: TypeNode;
isOptional?: boolean;
}
namespace InferFromReference {
interface CallContext {
argumentTypes: Type[];
@@ -246,7 +293,7 @@ namespace ts.codefix {
return getTypeFromUsageContext(usageContext, checker);
}
export function inferTypeForParametersFromReferences(references: ReadonlyArray<Identifier>, declaration: FunctionLikeDeclaration, checker: TypeChecker, cancellationToken: CancellationToken): (Type | undefined)[] | undefined {
export function inferTypeForParametersFromReferences(references: ReadonlyArray<Identifier>, declaration: FunctionLikeDeclaration, checker: TypeChecker, cancellationToken: CancellationToken): ParameterInference[] | undefined {
if (references.length === 0) {
return undefined;
}
@@ -265,8 +312,10 @@ namespace ts.codefix {
return callContexts && declaration.parameters.map((parameter, parameterIndex) => {
const types: Type[] = [];
const isRest = isRestParameter(parameter);
let isOptional = false;
for (const callContext of callContexts) {
if (callContext.argumentTypes.length <= parameterIndex) {
isOptional = isInJSFile(declaration);
continue;
}
@@ -280,10 +329,14 @@ namespace ts.codefix {
}
}
if (!types.length) {
return undefined;
return { declaration: parameter };
}
const type = checker.getWidenedType(checker.getUnionType(types, UnionReduction.Subtype));
return isRest ? checker.createArrayType(type) : type;
return {
type: isRest ? checker.createArrayType(type) : type,
isOptional: isOptional && !isRest,
declaration: parameter
};
});
}
+40 -26
View File
@@ -43,7 +43,7 @@ namespace ts.Completions {
}
const contextToken = findPrecedingToken(position, sourceFile);
if (triggerCharacter && (!contextToken || !isValidTrigger(sourceFile, triggerCharacter, contextToken, position))) return undefined;
if (triggerCharacter && !isValidTrigger(sourceFile, triggerCharacter, contextToken, position)) return undefined;
if (isInString(sourceFile, position, contextToken)) {
return !contextToken || !isStringLiteralLike(contextToken)
@@ -134,7 +134,7 @@ namespace ts.Completions {
if (isUncheckedFile(sourceFile, compilerOptions)) {
const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries, location, sourceFile, typeChecker, compilerOptions.target!, log, completionKind, preferences, propertyAccessToConvert, isJsxInitializer, recommendedCompletion, symbolToOriginInfoMap);
getJavaScriptCompletionEntries(sourceFile, location!.pos, uniqueNames, compilerOptions.target!, entries); // TODO: GH#18217
getJSCompletionEntries(sourceFile, location!.pos, uniqueNames, compilerOptions.target!, entries); // TODO: GH#18217
}
else {
if ((!symbols || symbols.length === 0) && keywordFilters === KeywordCompletionFilters.None) {
@@ -161,7 +161,7 @@ namespace ts.Completions {
}
function isUncheckedFile(sourceFile: SourceFile, compilerOptions: CompilerOptions): boolean {
return isSourceFileJavaScript(sourceFile) && !isCheckJsEnabledForFile(sourceFile, compilerOptions);
return isSourceFileJS(sourceFile) && !isCheckJsEnabledForFile(sourceFile, compilerOptions);
}
function isMemberCompletionKind(kind: CompletionKind): boolean {
@@ -175,7 +175,7 @@ namespace ts.Completions {
}
}
function getJavaScriptCompletionEntries(
function getJSCompletionEntries(
sourceFile: SourceFile,
position: number,
uniqueNames: Map<true>,
@@ -390,11 +390,12 @@ namespace ts.Completions {
}
type StringLiteralCompletion = { readonly kind: StringLiteralCompletionKind.Paths, readonly paths: ReadonlyArray<PathCompletions.PathCompletion> } | StringLiteralCompletionsFromProperties | StringLiteralCompletionsFromTypes;
function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringLiteralLike, position: number, typeChecker: TypeChecker, compilerOptions: CompilerOptions, host: LanguageServiceHost): StringLiteralCompletion | undefined {
switch (node.parent.kind) {
const { parent } = node;
switch (parent.kind) {
case SyntaxKind.LiteralType:
switch (node.parent.parent.kind) {
switch (parent.parent.kind) {
case SyntaxKind.TypeReference:
return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(node.parent as LiteralTypeNode)), isNewIdentifier: false };
return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(parent as LiteralTypeNode)), isNewIdentifier: false };
case SyntaxKind.IndexedAccessType:
// Get all apparent property names
// i.e. interface Foo {
@@ -402,17 +403,21 @@ namespace ts.Completions {
// bar: string;
// }
// let x: Foo["/*completion position*/"]
return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode((node.parent.parent as IndexedAccessTypeNode).objectType));
return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode((parent.parent as IndexedAccessTypeNode).objectType));
case SyntaxKind.ImportType:
return { kind: StringLiteralCompletionKind.Paths, paths: PathCompletions.getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker) };
case SyntaxKind.UnionType:
return isTypeReferenceNode(node.parent.parent.parent) ? { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(node.parent.parent as UnionTypeNode)), isNewIdentifier: false } : undefined;
case SyntaxKind.UnionType: {
if (!isTypeReferenceNode(parent.parent.parent)) return undefined;
const alreadyUsedTypes = getAlreadyUsedTypesInStringLiteralUnion(parent.parent as UnionTypeNode, parent as LiteralTypeNode);
const types = getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(parent.parent as UnionTypeNode)).filter(t => !contains(alreadyUsedTypes, t.value));
return { kind: StringLiteralCompletionKind.Types, types, isNewIdentifier: false };
}
default:
return undefined;
}
case SyntaxKind.PropertyAssignment:
if (isObjectLiteralExpression(node.parent.parent) && (<PropertyAssignment>node.parent).name === node) {
if (isObjectLiteralExpression(parent.parent) && (<PropertyAssignment>parent).name === node) {
// Get quoted name of properties of the object literal expression
// i.e. interface ConfigFiles {
// 'jspm:dev': string
@@ -425,12 +430,12 @@ namespace ts.Completions {
// foo({
// '/*completion position*/'
// });
return stringLiteralCompletionsFromProperties(typeChecker.getContextualType(node.parent.parent));
return stringLiteralCompletionsFromProperties(typeChecker.getContextualType(parent.parent));
}
return fromContextualType();
case SyntaxKind.ElementAccessExpression: {
const { expression, argumentExpression } = node.parent as ElementAccessExpression;
const { expression, argumentExpression } = parent as ElementAccessExpression;
if (node === argumentExpression) {
// Get all names of properties on the expression
// i.e. interface A {
@@ -445,7 +450,7 @@ namespace ts.Completions {
case SyntaxKind.CallExpression:
case SyntaxKind.NewExpression:
if (!isRequireCall(node.parent, /*checkArgumentIsStringLiteralLike*/ false) && !isImportCall(node.parent)) {
if (!isRequireCall(parent, /*checkArgumentIsStringLiteralLike*/ false) && !isImportCall(parent)) {
const argumentInfo = SignatureHelp.getArgumentInfoForCompletions(node, position, sourceFile);
// Get string literal completions from specialized signatures of the target
// i.e. declare function f(a: 'A');
@@ -476,6 +481,11 @@ namespace ts.Completions {
}
}
function getAlreadyUsedTypesInStringLiteralUnion(union: UnionTypeNode, current: LiteralTypeNode): ReadonlyArray<string> {
return mapDefined(union.types, type =>
type !== current && isLiteralTypeNode(type) && isStringLiteral(type.literal) ? type.literal.text : undefined);
}
function getStringLiteralCompletionsFromSignature(argumentInfo: SignatureHelp.ArgumentInfoForCompletions, checker: TypeChecker): StringLiteralCompletionsFromTypes {
let isNewIdentifier = false;
@@ -1190,9 +1200,9 @@ namespace ts.Completions {
function tryGetJsxCompletionSymbols(): GlobalsSearch {
const jsxContainer = tryGetContainingJsxElement(contextToken);
// Cursor is inside a JSX self-closing element or opening element
const attrsType = jsxContainer && typeChecker.getAllAttributesTypeFromJsxOpeningLikeElement(jsxContainer);
const attrsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes);
if (!attrsType) return GlobalsSearch.Continue;
symbols = filterJsxAttributes(typeChecker.getPropertiesOfType(attrsType), jsxContainer!.attributes.properties);
symbols = filterJsxAttributes(getPropertiesForObjectExpression(attrsType, jsxContainer!.attributes, typeChecker), jsxContainer!.attributes.properties);
completionKind = CompletionKind.MemberLike;
isNewIdentifierLocation = false;
return GlobalsSearch.Success;
@@ -1268,10 +1278,10 @@ namespace ts.Completions {
if (sourceFile.externalModuleIndicator) return true;
// If already using commonjs, don't introduce ES6.
if (sourceFile.commonJsModuleIndicator) return false;
// If some file is using ES6 modules, assume that it's OK to add more.
if (programContainsEs6Modules(program)) return true;
// For JS, stay on the safe side.
if (isUncheckedFile) return false;
// If some file is using ES6 modules, assume that it's OK to add more.
if (programContainsEs6Modules(program)) return true;
// If module transpilation is enabled or we're targeting es6 or above, or not emitting, OK.
return compilerOptionsIndicateEs6Modules(program.getCompilerOptions());
}
@@ -1387,7 +1397,7 @@ namespace ts.Completions {
if (resolvedModuleSymbol !== moduleSymbol &&
// Don't add another completion for `export =` of a symbol that's already global.
// So in `declare namespace foo {} declare module "foo" { export = foo; }`, there will just be the global completion for `foo`.
resolvedModuleSymbol.declarations.some(d => !!d.getSourceFile().externalModuleIndicator)) {
some(resolvedModuleSymbol.declarations, d => !!d.getSourceFile().externalModuleIndicator)) {
symbols.push(resolvedModuleSymbol);
symbolToOriginInfoMap[getSymbolId(resolvedModuleSymbol)] = { kind: SymbolOriginInfoKind.Export, moduleSymbol, isDefaultExport: false };
}
@@ -1703,6 +1713,9 @@ namespace ts.Completions {
break;
case SyntaxKind.AsteriskToken:
return isMethodDeclaration(parent) ? tryCast(parent.parent, isObjectLiteralExpression) : undefined;
case SyntaxKind.Identifier:
return (contextToken as Identifier).text === "async" && isShorthandPropertyAssignment(contextToken.parent)
? contextToken.parent.parent : undefined;
}
}
@@ -1918,7 +1931,6 @@ namespace ts.Completions {
// Previous token may have been a keyword that was converted to an identifier.
switch (keywordForNode(contextToken)) {
case SyntaxKind.AbstractKeyword:
case SyntaxKind.AsyncKeyword:
case SyntaxKind.ClassKeyword:
case SyntaxKind.ConstKeyword:
case SyntaxKind.DeclareKeyword:
@@ -1933,6 +1945,8 @@ namespace ts.Completions {
case SyntaxKind.VarKeyword:
case SyntaxKind.YieldKeyword:
return true;
case SyntaxKind.AsyncKeyword:
return isPropertyDeclaration(contextToken.parent);
}
return isDeclarationName(contextToken)
@@ -2197,7 +2211,7 @@ namespace ts.Completions {
return jsdoc && jsdoc.tags && (rangeContainsPosition(jsdoc, position) ? findLast(jsdoc.tags, tag => tag.pos < position) : undefined);
}
function getPropertiesForObjectExpression(contextualType: Type, obj: ObjectLiteralExpression, checker: TypeChecker): Symbol[] {
function getPropertiesForObjectExpression(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker): Symbol[] {
return contextualType.isUnion()
? checker.getAllPossiblePropertiesOfTypes(contextualType.types.filter(memberType =>
// If we're providing completions for an object literal, skip primitive, array-like, or callable types since those shouldn't be implemented by object literals.
@@ -2262,7 +2276,7 @@ namespace ts.Completions {
return !!type.getStringIndexType() || !!type.getNumberIndexType();
}
function isValidTrigger(sourceFile: SourceFile, triggerCharacter: CompletionsTriggerCharacter, contextToken: Node, position: number): boolean {
function isValidTrigger(sourceFile: SourceFile, triggerCharacter: CompletionsTriggerCharacter, contextToken: Node | undefined, position: number): boolean {
switch (triggerCharacter) {
case ".":
case "@":
@@ -2271,14 +2285,14 @@ namespace ts.Completions {
case "'":
case "`":
// Only automatically bring up completions if this is an opening quote.
return isStringLiteralOrTemplate(contextToken) && position === contextToken.getStart(sourceFile) + 1;
return !!contextToken && isStringLiteralOrTemplate(contextToken) && position === contextToken.getStart(sourceFile) + 1;
case "<":
// Opening JSX tag
return contextToken.kind === SyntaxKind.LessThanToken && (!isBinaryExpression(contextToken.parent) || binaryExpressionMayBeOpenTag(contextToken.parent));
return !!contextToken && contextToken.kind === SyntaxKind.LessThanToken && (!isBinaryExpression(contextToken.parent) || binaryExpressionMayBeOpenTag(contextToken.parent));
case "/":
return isStringLiteralLike(contextToken)
return !!contextToken && (isStringLiteralLike(contextToken)
? !!tryGetImportFromModuleSpecifier(contextToken)
: contextToken.kind === SyntaxKind.SlashToken && isJsxClosingElement(contextToken.parent);
: contextToken.kind === SyntaxKind.SlashToken && isJsxClosingElement(contextToken.parent));
default:
return Debug.assertNever(triggerCharacter);
}
+4 -4
View File
@@ -121,10 +121,6 @@ namespace ts {
const buckets = createMap<Map<DocumentRegistryEntry>>();
const getCanonicalFileName = createGetCanonicalFileName(!!useCaseSensitiveFileNames);
function getKeyForCompilationSettings(settings: CompilerOptions): DocumentRegistryBucketKey {
return <DocumentRegistryBucketKey>`_${settings.target}|${settings.module}|${settings.noResolve}|${settings.jsx}|${settings.allowJs}|${settings.baseUrl}|${JSON.stringify(settings.typeRoots)}|${JSON.stringify(settings.rootDirs)}|${JSON.stringify(settings.paths)}`;
}
function getBucketForCompilationSettings(key: DocumentRegistryBucketKey, createIfMissing: boolean): Map<DocumentRegistryEntry> {
let bucket = buckets.get(key);
if (!bucket && createIfMissing) {
@@ -273,4 +269,8 @@ namespace ts {
getKeyForCompilationSettings
};
}
function getKeyForCompilationSettings(settings: CompilerOptions): DocumentRegistryBucketKey {
return sourceFileAffectingCompilerOptions.map(option => getCompilerOptionValue(settings, option)).join("|") as DocumentRegistryBucketKey;
}
}
+125 -82
View File
@@ -13,19 +13,20 @@ namespace ts.FindAllReferences {
| { readonly type: DefinitionKind.This; readonly node: Node }
| { readonly type: DefinitionKind.String; readonly node: StringLiteral };
export const enum EntryKind { Span, Node, StringLiteral, SearchedLocalFoundProperty, SearchedPropertyFoundLocal }
export type NodeEntryKind = EntryKind.Node | EntryKind.StringLiteral | EntryKind.SearchedLocalFoundProperty | EntryKind.SearchedPropertyFoundLocal;
export type Entry = NodeEntry | SpanEntry;
export interface NodeEntry {
type: "node";
node: Node;
isInString?: true;
readonly kind: NodeEntryKind;
readonly node: Node;
}
export interface SpanEntry {
type: "span";
fileName: string;
textSpan: TextSpan;
readonly kind: EntryKind.Span;
readonly fileName: string;
readonly textSpan: TextSpan;
}
export function nodeEntry(node: Node, isInString?: true): NodeEntry {
return { type: "node", node: (node as NamedDeclaration).name || node, isInString };
export function nodeEntry(node: Node, kind: NodeEntryKind = EntryKind.Node): NodeEntry {
return { kind, node: (node as NamedDeclaration).name || node };
}
export interface Options {
@@ -33,7 +34,7 @@ namespace ts.FindAllReferences {
readonly findInComments?: boolean;
/**
* True if we are renaming the symbol.
* If so, we will find fewer references -- if it is referenced by several different names, we sill only find references for the original name.
* If so, we will find fewer references -- if it is referenced by several different names, we still only find references for the original name.
*/
readonly isForRename?: boolean;
/** True if we are searching for implementations. We will have a different method of adding references if so. */
@@ -84,10 +85,15 @@ namespace ts.FindAllReferences {
}
}
export function findReferencedEntries(program: Program, cancellationToken: CancellationToken, sourceFiles: ReadonlyArray<SourceFile>, node: Node, position: number, options: Options | undefined): ReferenceEntry[] | undefined {
return map(flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options)), toReferenceEntry);
export function findReferenceOrRenameEntries<T>(
program: Program, cancellationToken: CancellationToken, sourceFiles: ReadonlyArray<SourceFile>, node: Node, position: number, options: Options | undefined,
convertEntry: ToReferenceOrRenameEntry<T>,
): T[] | undefined {
return map(flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options)), entry => convertEntry(entry, node));
}
export type ToReferenceOrRenameEntry<T> = (entry: Entry, originalNode: Node) => T;
export function getReferenceEntriesForNode(position: number, node: Node, program: Program, sourceFiles: ReadonlyArray<SourceFile>, cancellationToken: CancellationToken, options: Options = {}, sourceFilesSet: ReadonlyMap<true> = arrayToSet(sourceFiles, f => f.fileName)): Entry[] | undefined {
return flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options, sourceFilesSet));
}
@@ -143,26 +149,64 @@ namespace ts.FindAllReferences {
return { displayParts, kind: symbolKind };
}
function toReferenceEntry(entry: Entry): ReferenceEntry {
if (entry.type === "span") {
return { textSpan: entry.textSpan, fileName: entry.fileName, isWriteAccess: false, isDefinition: false };
}
export function toRenameLocation(entry: Entry, originalNode: Node): RenameLocation {
return { ...entryToDocumentSpan(entry), ...getPrefixAndSuffixText(entry, originalNode) };
}
const { node, isInString } = entry;
const sourceFile = node.getSourceFile();
export function toReferenceEntry(entry: Entry): ReferenceEntry {
const { textSpan, fileName } = entryToDocumentSpan(entry);
if (entry.kind === EntryKind.Span) {
return { textSpan, fileName, isWriteAccess: false, isDefinition: false };
}
const { kind, node } = entry;
return {
fileName: sourceFile.fileName,
textSpan: getTextSpan(node, sourceFile),
textSpan,
fileName,
isWriteAccess: isWriteAccessForReference(node),
isDefinition: node.kind === SyntaxKind.DefaultKeyword
|| !!getDeclarationFromName(node)
|| isLiteralComputedPropertyDeclarationName(node),
isInString,
isInString: kind === EntryKind.StringLiteral ? true : undefined,
};
}
function entryToDocumentSpan(entry: Entry): DocumentSpan {
if (entry.kind === EntryKind.Span) {
return { textSpan: entry.textSpan, fileName: entry.fileName };
}
else {
const sourceFile = entry.node.getSourceFile();
return { textSpan: getTextSpan(entry.node, sourceFile), fileName: sourceFile.fileName };
}
}
function getPrefixAndSuffixText(entry: Entry, originalNode: Node): { readonly prefixText?: string, readonly suffixText?: string } {
if (entry.kind !== EntryKind.Span && isIdentifier(originalNode)) {
const { node, kind } = entry;
const name = originalNode.text;
const isShorthandAssignment = isShorthandPropertyAssignment(node.parent);
if (isShorthandAssignment || isObjectBindingElementWithoutPropertyName(node.parent)) {
if (kind === EntryKind.SearchedLocalFoundProperty) {
return { prefixText: name + ": " };
}
else if (kind === EntryKind.SearchedPropertyFoundLocal) {
return { suffixText: ": " + name };
}
else {
return isShorthandAssignment
// In `const o = { x }; o.x`, symbolAtLocation at `x` in `{ x }` is the property symbol.
? { suffixText: ": " + name }
// For a binding element `const { x } = o;`, symbolAtLocation at `x` is the property symbol.
: { prefixText: name + ": " };
}
}
}
return emptyOptions;
}
function toImplementationLocation(entry: Entry, checker: TypeChecker): ImplementationLocation {
if (entry.type === "node") {
if (entry.kind !== EntryKind.Span) {
const { node } = entry;
const sourceFile = node.getSourceFile();
return { textSpan: getTextSpan(node, sourceFile), fileName: sourceFile.fileName, ...implementationKindDisplayParts(node, checker) };
@@ -196,18 +240,18 @@ namespace ts.FindAllReferences {
}
export function toHighlightSpan(entry: Entry): { fileName: string, span: HighlightSpan } {
if (entry.type === "span") {
if (entry.kind === EntryKind.Span) {
const { fileName, textSpan } = entry;
return { fileName, span: { textSpan, kind: HighlightSpanKind.reference } };
}
const { node, isInString } = entry;
const { node, kind } = entry;
const sourceFile = node.getSourceFile();
const writeAccess = isWriteAccessForReference(node);
const span: HighlightSpan = {
textSpan: getTextSpan(node, sourceFile),
kind: writeAccess ? HighlightSpanKind.writtenReference : HighlightSpanKind.reference,
isInString
isInString: kind === EntryKind.StringLiteral ? true : undefined,
};
return { fileName: sourceFile.fileName, span };
}
@@ -348,11 +392,11 @@ namespace ts.FindAllReferences.Core {
}
}
// import("foo") with no qualifier will reference the `export =` of the module, which may be referenced anyway.
return { type: "node", node: reference.literal };
return nodeEntry(reference.literal);
}
else {
return {
type: "span",
kind: EntryKind.Span,
fileName: reference.referencingFile.fileName,
textSpan: createTextSpanFromRange(reference.ref),
};
@@ -366,7 +410,7 @@ namespace ts.FindAllReferences.Core {
break;
case SyntaxKind.ModuleDeclaration:
if (sourceFilesSet.has(decl.getSourceFile().fileName)) {
references.push({ type: "node", node: (decl as ModuleDeclaration).name });
references.push(nodeEntry((decl as ModuleDeclaration).name));
}
break;
default:
@@ -422,7 +466,7 @@ namespace ts.FindAllReferences.Core {
searchForImportsOfExport(node, symbol, { exportingModuleSymbol: Debug.assertDefined(symbol.parent, "Expected export symbol to have a parent"), exportKind: ExportKind.Default }, state);
}
else {
const search = state.createSearch(node, symbol, /*comingFrom*/ undefined, { allSearchSymbols: node ? populateSearchSymbolSet(symbol, node, checker, !!options.implementations) : [symbol] });
const search = state.createSearch(node, symbol, /*comingFrom*/ undefined, { allSearchSymbols: node ? populateSearchSymbolSet(symbol, node, checker, !!options.isForRename, !!options.implementations) : [symbol] });
// Try to get the smallest valid scope that we can limit our search to;
// otherwise we'll need to search globally (i.e. include each file).
@@ -581,21 +625,21 @@ namespace ts.FindAllReferences.Core {
* Callback to add references for a particular searched symbol.
* This initializes a reference group, so only call this if you will add at least one reference.
*/
referenceAdder(searchSymbol: Symbol): (node: Node) => void {
referenceAdder(searchSymbol: Symbol): (node: Node, kind?: NodeEntryKind) => void {
const symbolId = getSymbolId(searchSymbol);
let references = this.symbolIdToReferences[symbolId];
if (!references) {
references = this.symbolIdToReferences[symbolId] = [];
this.result.push({ definition: { type: DefinitionKind.Symbol, symbol: searchSymbol }, references });
}
return node => references.push(nodeEntry(node));
return (node, kind) => references.push(nodeEntry(node, kind));
}
/** Add a reference with no associated definition. */
addStringOrCommentReference(fileName: string, textSpan: TextSpan): void {
this.result.push({
definition: undefined,
references: [{ type: "span", fileName, textSpan }]
references: [{ kind: EntryKind.Span, fileName, textSpan }]
});
}
@@ -707,21 +751,6 @@ namespace ts.FindAllReferences.Core {
: undefined;
}
function getObjectBindingElementWithoutPropertyName(symbol: Symbol): BindingElement & { name: Identifier } | undefined {
const bindingElement = getDeclarationOfKind<BindingElement>(symbol, SyntaxKind.BindingElement);
if (bindingElement &&
bindingElement.parent.kind === SyntaxKind.ObjectBindingPattern &&
isIdentifier(bindingElement.name) &&
!bindingElement.propertyName) {
return bindingElement as BindingElement & { name: Identifier };
}
}
function getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol: Symbol, checker: TypeChecker): Symbol | undefined {
const bindingElement = getObjectBindingElementWithoutPropertyName(symbol);
return bindingElement && getPropertySymbolFromBindingElement(checker, bindingElement);
}
/**
* Determines the smallest scope in which a symbol may have named references.
* Note that not every construct has been accounted for. This function can
@@ -754,7 +783,7 @@ namespace ts.FindAllReferences.Core {
// If symbol is of object binding pattern element without property name we would want to
// look for property too and that could be anywhere
if (getObjectBindingElementWithoutPropertyName(symbol)) {
if (declarations.some(isObjectBindingElementWithoutPropertyName)) {
return undefined;
}
@@ -1099,13 +1128,14 @@ namespace ts.FindAllReferences.Core {
}
}
function addReference(referenceLocation: Node, relatedSymbol: Symbol, state: State): void {
const addRef = state.referenceAdder(relatedSymbol);
function addReference(referenceLocation: Node, relatedSymbol: Symbol | RelatedSymbol, state: State): void {
const { kind, symbol } = "kind" in relatedSymbol ? relatedSymbol : { kind: undefined, symbol: relatedSymbol };
const addRef = state.referenceAdder(symbol);
if (state.options.implementations) {
addImplementationReferences(referenceLocation, addRef, state);
}
else {
addRef(referenceLocation);
addRef(referenceLocation, kind);
}
}
@@ -1428,7 +1458,7 @@ namespace ts.FindAllReferences.Core {
const references = flatMap(sourceFiles, sourceFile => {
cancellationToken.throwIfCancellationRequested();
return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, node.text), ref =>
isStringLiteral(ref) && ref.text === node.text ? nodeEntry(ref, /*isInString*/ true) : undefined);
isStringLiteral(ref) && ref.text === node.text ? nodeEntry(ref, EntryKind.StringLiteral) : undefined);
});
return [{
@@ -1439,35 +1469,21 @@ namespace ts.FindAllReferences.Core {
// For certain symbol kinds, we need to include other symbols in the search set.
// This is not needed when searching for re-exports.
function populateSearchSymbolSet(symbol: Symbol, location: Node, checker: TypeChecker, implementations: boolean): Symbol[] {
function populateSearchSymbolSet(symbol: Symbol, location: Node, checker: TypeChecker, isForRename: boolean, implementations: boolean): Symbol[] {
const result: Symbol[] = [];
forEachRelatedSymbol<void>(symbol, location, checker,
forEachRelatedSymbol<void>(symbol, location, checker, isForRename,
(sym, root, base) => { result.push(base || root || sym); },
/*allowBaseTypes*/ () => !implementations);
return result;
}
function forEachRelatedSymbol<T>(
symbol: Symbol, location: Node, checker: TypeChecker,
cbSymbol: (symbol: Symbol, rootSymbol?: Symbol, baseSymbol?: Symbol) => T | undefined,
symbol: Symbol, location: Node, checker: TypeChecker, isForRenamePopulateSearchSymbolSet: boolean,
cbSymbol: (symbol: Symbol, rootSymbol?: Symbol, baseSymbol?: Symbol, kind?: NodeEntryKind) => T | undefined,
allowBaseTypes: (rootSymbol: Symbol) => boolean,
): T | undefined {
const containingObjectLiteralElement = getContainingObjectLiteralElement(location);
if (containingObjectLiteralElement) {
// If the location is in a context sensitive location (i.e. in an object literal) try
// to get a contextual type for it, and add the property symbol from the contextual
// type to the search set
const contextualType = checker.getContextualType(containingObjectLiteralElement.parent);
const res = contextualType && firstDefined(getPropertySymbolsFromContextualType(containingObjectLiteralElement, checker, contextualType, /*unionSymbolOk*/ true), fromRoot);
if (res) return res;
// If the location is name of property symbol from object literal destructuring pattern
// Search the property symbol
// for ( { property: p2 } of elems) { }
const propertySymbol = getPropertySymbolOfDestructuringAssignment(location, checker);
const res1 = propertySymbol && cbSymbol(propertySymbol);
if (res1) return res1;
/* Because in short-hand property assignment, location has two meaning : property name and as value of the property
* When we do findAllReference at the position of the short-hand property assignment, we would want to have references to position of
* property name and variable declaration of the identifier.
@@ -1479,8 +1495,29 @@ namespace ts.FindAllReferences.Core {
* so that when matching with potential reference symbol, both symbols from property declaration and variable declaration
* will be included correctly.
*/
const shorthandValueSymbol = checker.getShorthandAssignmentValueSymbol(location.parent);
const res2 = shorthandValueSymbol && cbSymbol(shorthandValueSymbol);
const shorthandValueSymbol = checker.getShorthandAssignmentValueSymbol(location.parent); // gets the local symbol
if (shorthandValueSymbol && isForRenamePopulateSearchSymbolSet) {
// When renaming 'x' in `const o = { x }`, just rename the local variable, not the property.
return cbSymbol(shorthandValueSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedLocalFoundProperty);
}
// If the location is in a context sensitive location (i.e. in an object literal) try
// to get a contextual type for it, and add the property symbol from the contextual
// type to the search set
const contextualType = checker.getContextualType(containingObjectLiteralElement.parent);
const res = contextualType && firstDefined(
getPropertySymbolsFromContextualType(containingObjectLiteralElement, checker, contextualType, /*unionSymbolOk*/ true),
sym => fromRoot(sym, EntryKind.SearchedPropertyFoundLocal));
if (res) return res;
// If the location is name of property symbol from object literal destructuring pattern
// Search the property symbol
// for ( { property: p2 } of elems) { }
const propertySymbol = getPropertySymbolOfDestructuringAssignment(location, checker);
const res1 = propertySymbol && cbSymbol(propertySymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedPropertyFoundLocal);
if (res1) return res1;
const res2 = shorthandValueSymbol && cbSymbol(shorthandValueSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedLocalFoundProperty);
if (res2) return res2;
}
@@ -1494,12 +1531,14 @@ namespace ts.FindAllReferences.Core {
return fromRoot(symbol.flags & SymbolFlags.FunctionScopedVariable ? paramProps[1] : paramProps[0]);
}
// If this is symbol of binding element without propertyName declaration in Object binding pattern
// Include the property in the search
const bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, checker);
return bindingElementPropertySymbol && fromRoot(bindingElementPropertySymbol);
// symbolAtLocation for a binding element is the local symbol. See if the search symbol is the property.
// Don't do this when populating search set for a rename -- just rename the local.
if (!isForRenamePopulateSearchSymbolSet) {
const bindingElementPropertySymbol = isObjectBindingElementWithoutPropertyName(location.parent) ? getPropertySymbolFromBindingElement(checker, location.parent) : undefined;
return bindingElementPropertySymbol && fromRoot(bindingElementPropertySymbol, EntryKind.SearchedPropertyFoundLocal);
}
function fromRoot(sym: Symbol): T | undefined {
function fromRoot(sym: Symbol, kind?: NodeEntryKind): T | undefined {
// If this is a union property:
// - In populateSearchSymbolsSet we will add all the symbols from all its source symbols in all unioned types.
// - In findRelatedSymbol, we will just use the union symbol if any source symbol is included in the search.
@@ -1507,20 +1546,24 @@ namespace ts.FindAllReferences.Core {
// - In populateSearchSymbolsSet, add the root the list
// - In findRelatedSymbol, return the source symbol if that is in the search. (Do not return the instantiation symbol.)
return firstDefined(checker.getRootSymbols(sym), rootSymbol =>
cbSymbol(sym, rootSymbol)
cbSymbol(sym, rootSymbol, /*baseSymbol*/ undefined, kind)
// Add symbol of properties/methods of the same name in base classes and implemented interfaces definitions
|| (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface) && allowBaseTypes(rootSymbol)
? getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.name, checker, base => cbSymbol(sym, rootSymbol, base))
? getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.name, checker, base => cbSymbol(sym, rootSymbol, base, kind))
: undefined));
}
}
function getRelatedSymbol(search: Search, referenceSymbol: Symbol, referenceLocation: Node, state: State): Symbol | undefined {
interface RelatedSymbol {
readonly symbol: Symbol;
readonly kind: NodeEntryKind | undefined;
}
function getRelatedSymbol(search: Search, referenceSymbol: Symbol, referenceLocation: Node, state: State): RelatedSymbol | undefined {
const { checker } = state;
return forEachRelatedSymbol(referenceSymbol, referenceLocation, checker,
(sym, rootSymbol, baseSymbol) => search.includes(baseSymbol || rootSymbol || sym)
return forEachRelatedSymbol(referenceSymbol, referenceLocation, checker, /*isForRenamePopulateSearchSymbolSet*/ false,
(sym, rootSymbol, baseSymbol, kind): RelatedSymbol | undefined => search.includes(baseSymbol || rootSymbol || sym)
// For a base type, use the symbol for the derived type. For a synthetic (e.g. union) property, use the union symbol.
? rootSymbol && !(getCheckFlags(sym) & CheckFlags.Synthetic) ? rootSymbol : sym
? { symbol: rootSymbol && !(getCheckFlags(sym) & CheckFlags.Synthetic) ? rootSymbol : sym, kind }
: undefined,
/*allowBaseTypes*/ rootSymbol =>
!(search.parents && !search.parents.some(parent => explicitlyInheritsFrom(rootSymbol.parent!, parent, state.inheritsFromCache, checker))));
+3 -3
View File
@@ -753,17 +753,17 @@ namespace ts.formatting {
const listEndToken = getCloseTokenForOpenToken(listStartToken);
if (listEndToken !== SyntaxKind.Unknown && formattingScanner.isOnToken()) {
let tokenInfo = formattingScanner.readTokenInfo(parent);
let tokenInfo: TokenInfo | undefined = formattingScanner.readTokenInfo(parent);
if (tokenInfo.token.kind === SyntaxKind.CommaToken && isCallLikeExpression(parent)) {
formattingScanner.advance();
tokenInfo = formattingScanner.readTokenInfo(parent);
tokenInfo = formattingScanner.isOnToken() ? formattingScanner.readTokenInfo(parent) : undefined;
}
// consume the list end token only if it is still belong to the parent
// there might be the case when current token matches end token but does not considered as one
// function (x: function) <--
// without this check close paren will be interpreted as list end token for function expression which is wrong
if (tokenInfo.token.kind === listEndToken && rangeContainsRange(parent, tokenInfo.token)) {
if (tokenInfo && tokenInfo.token.kind === listEndToken && rangeContainsRange(parent, tokenInfo.token)) {
// consume list end token
consumeTokenAndAdvanceScanner(tokenInfo, parent, listDynamicIndentation, parent);
}
+16 -8
View File
@@ -72,7 +72,7 @@ namespace ts {
case "compilerOptions":
forEachProperty(property.initializer, (property, propertyName) => {
const option = getOptionFromName(propertyName);
if (option && (option.isFilePath || option.type === "list" && (option as CommandLineOptionOfListType).element.isFilePath)) {
if (option && (option.isFilePath || option.type === "list" && option.element.isFilePath)) {
updatePaths(property);
}
else if (propertyName === "paths") {
@@ -196,15 +196,23 @@ namespace ts {
}
function getSourceFileToImportFromResolved(resolved: ResolvedModuleWithFailedLookupLocations | undefined, oldToNew: PathUpdater, host: LanguageServiceHost): ToImport | undefined {
return resolved && (
(resolved.resolvedModule && getIfExists(resolved.resolvedModule.resolvedFileName)) || firstDefined(resolved.failedLookupLocations, getIfExists));
// Search through all locations looking for a moved file, and only then test already existing files.
// This is because if `a.ts` is compiled to `a.js` and `a.ts` is moved, we don't want to resolve anything to `a.js`, but to `a.ts`'s new location.
return tryEach(tryGetNewFile) || tryEach(tryGetOldFile);
function getIfExists(oldLocation: string): ToImport | undefined {
const newLocation = oldToNew(oldLocation);
function tryEach(cb: (oldFileName: string) => ToImport | undefined): ToImport | undefined {
return resolved && (
(resolved.resolvedModule && cb(resolved.resolvedModule.resolvedFileName)) || firstDefined(resolved.failedLookupLocations, cb));
}
return host.fileExists!(oldLocation) || newLocation !== undefined && host.fileExists!(newLocation) // TODO: GH#18217
? newLocation !== undefined ? { newFileName: newLocation, updated: true } : { newFileName: oldLocation, updated: false }
: undefined;
function tryGetNewFile(oldFileName: string): ToImport | undefined {
const newFileName = oldToNew(oldFileName);
return newFileName !== undefined && host.fileExists!(newFileName) ? { newFileName, updated: true } : undefined; // TODO: GH#18217
}
function tryGetOldFile(oldFileName: string): ToImport | undefined {
const newFileName = oldToNew(oldFileName);
return host.fileExists!(oldFileName) ? newFileName !== undefined ? { newFileName, updated: true } : { newFileName: oldFileName, updated: false } : undefined; // TODO: GH#18217
}
}
+4 -4
View File
@@ -385,7 +385,7 @@ namespace ts.FindAllReferences {
}
/** Iterates over all statements at the top level or in module declarations. Returns the first truthy result. */
function forEachPossibleImportOrExportStatement<T>(sourceFileLike: SourceFileLike, action: (statement: Statement) => T): T | undefined {
function forEachPossibleImportOrExportStatement<T>(sourceFileLike: SourceFileLike, action: (statement: Statement) => T) {
return forEach(sourceFileLike.kind === SyntaxKind.SourceFile ? sourceFileLike.statements : sourceFileLike.body!.statements, statement => // TODO: GH#18217
action(statement) || (isAmbientModuleDeclaration(statement) && forEach(statement.body && statement.body.statements, action)));
}
@@ -502,11 +502,11 @@ namespace ts.FindAllReferences {
function getSpecialPropertyExport(node: BinaryExpression, useLhsSymbol: boolean): ExportedSymbol | undefined {
let kind: ExportKind;
switch (getSpecialPropertyAssignmentKind(node)) {
case SpecialPropertyAssignmentKind.ExportsProperty:
switch (getAssignmentDeclarationKind(node)) {
case AssignmentDeclarationKind.ExportsProperty:
kind = ExportKind.Named;
break;
case SpecialPropertyAssignmentKind.ModuleExports:
case AssignmentDeclarationKind.ModuleExports:
kind = ExportKind.ExportEquals;
break;
default:
+4 -4
View File
@@ -208,7 +208,7 @@ namespace ts.JsDoc {
kindModifiers: "",
displayParts: [textPart(name)],
documentation: emptyArray,
tags: emptyArray,
tags: undefined,
codeActions: undefined,
};
}
@@ -242,7 +242,7 @@ namespace ts.JsDoc {
kindModifiers: "",
displayParts: [textPart(name)],
documentation: emptyArray,
tags: emptyArray,
tags: undefined,
codeActions: undefined,
};
}
@@ -312,7 +312,7 @@ namespace ts.JsDoc {
const preamble = "/**" + newLine + indentationStr + " * ";
const result =
preamble + newLine +
parameterDocComments(parameters, hasJavaScriptFileExtension(sourceFile.fileName), indentationStr, newLine) +
parameterDocComments(parameters, hasJSFileExtension(sourceFile.fileName), indentationStr, newLine) +
indentationStr + " */" +
(tokenStart === position ? newLine + indentationStr : "");
@@ -383,7 +383,7 @@ namespace ts.JsDoc {
case SyntaxKind.BinaryExpression: {
const be = commentOwner as BinaryExpression;
if (getSpecialPropertyAssignmentKind(be) === SpecialPropertyAssignmentKind.None) {
if (getAssignmentDeclarationKind(be) === AssignmentDeclarationKind.None) {
return "quit";
}
const parameters = isFunctionLike(be.right) ? be.right.parameters : emptyArray;
+38 -16
View File
@@ -270,17 +270,17 @@ namespace ts.NavigationBar {
break;
case SyntaxKind.BinaryExpression: {
const special = getSpecialPropertyAssignmentKind(node as BinaryExpression);
const special = getAssignmentDeclarationKind(node as BinaryExpression);
switch (special) {
case SpecialPropertyAssignmentKind.ExportsProperty:
case SpecialPropertyAssignmentKind.ModuleExports:
case SpecialPropertyAssignmentKind.PrototypeProperty:
case SpecialPropertyAssignmentKind.Prototype:
case AssignmentDeclarationKind.ExportsProperty:
case AssignmentDeclarationKind.ModuleExports:
case AssignmentDeclarationKind.PrototypeProperty:
case AssignmentDeclarationKind.Prototype:
addNodeWithRecursiveChild(node, (node as BinaryExpression).right);
return;
case SpecialPropertyAssignmentKind.ThisProperty:
case SpecialPropertyAssignmentKind.Property:
case SpecialPropertyAssignmentKind.None:
case AssignmentDeclarationKind.ThisProperty:
case AssignmentDeclarationKind.Property:
case AssignmentDeclarationKind.None:
break;
default:
Debug.assertNever(special);
@@ -631,28 +631,50 @@ namespace ts.NavigationBar {
}
function getFunctionOrClassName(node: FunctionExpression | FunctionDeclaration | ArrowFunction | ClassLikeDeclaration): string {
const { parent } = node;
if (node.name && getFullWidth(node.name) > 0) {
return declarationNameToString(node.name);
}
// See if it is a var initializer. If so, use the var name.
else if (node.parent.kind === SyntaxKind.VariableDeclaration) {
return declarationNameToString((node.parent as VariableDeclaration).name);
else if (isVariableDeclaration(parent)) {
return declarationNameToString(parent.name);
}
// See if it is of the form "<expr> = function(){...}". If so, use the text from the left-hand side.
else if (node.parent.kind === SyntaxKind.BinaryExpression &&
(node.parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) {
return nodeText((node.parent as BinaryExpression).left).replace(whiteSpaceRegex, "");
else if (isBinaryExpression(parent) && parent.operatorToken.kind === SyntaxKind.EqualsToken) {
return nodeText(parent.left).replace(whiteSpaceRegex, "");
}
// See if it is a property assignment, and if so use the property name
else if (node.parent.kind === SyntaxKind.PropertyAssignment && (node.parent as PropertyAssignment).name) {
return nodeText((node.parent as PropertyAssignment).name);
else if (isPropertyAssignment(parent)) {
return nodeText(parent.name);
}
// Default exports are named "default"
else if (getModifierFlags(node) & ModifierFlags.Default) {
return "default";
}
else if (isClassLike(node)) {
return "<class>";
}
else if (isCallExpression(parent)) {
const name = getCalledExpressionName(parent.expression);
if (name !== undefined) {
const args = mapDefined(parent.arguments, a => isStringLiteral(a) ? a.getText(curSourceFile) : undefined).join(", ");
return `${name}(${args}) callback`;
}
}
return "<function>";
}
function getCalledExpressionName(expr: Expression): string | undefined {
if (isIdentifier(expr)) {
return expr.text;
}
else if (isPropertyAccessExpression(expr)) {
const left = getCalledExpressionName(expr.expression);
const right = expr.name.text;
return left === undefined ? right : `${left}.${right}`;
}
else {
return isClassLike(node) ? "<class>" : "<function>";
return undefined;
}
}
+48 -15
View File
@@ -151,11 +151,41 @@ namespace ts.Completions.PathCompletions {
}
}
}
// check for a version redirect
const packageJsonPath = findPackageJson(baseDirectory, host);
if (packageJsonPath) {
const packageJson = readJson(packageJsonPath, host as { readFile: (filename: string) => string | undefined });
const typesVersions = (packageJson as any).typesVersions;
if (typeof typesVersions === "object") {
const versionResult = getPackageJsonTypesVersionsPaths(typesVersions);
const versionPaths = versionResult && versionResult.paths;
const rest = absolutePath.slice(ensureTrailingDirectorySeparator(baseDirectory).length);
if (versionPaths) {
addCompletionEntriesFromPaths(result, rest, baseDirectory, extensions, versionPaths, host);
}
}
}
}
return result;
}
function addCompletionEntriesFromPaths(result: NameAndKind[], fragment: string, baseDirectory: string, fileExtensions: ReadonlyArray<string>, paths: MapLike<string[]>, host: LanguageServiceHost) {
for (const path in paths) {
if (!hasProperty(paths, path)) continue;
const patterns = paths[path];
if (patterns) {
for (const { name, kind } of getCompletionsForPathMapping(path, patterns, fragment, baseDirectory, fileExtensions, host)) {
// Path mappings may provide a duplicate way to get to something we've already added, so don't add again.
if (!result.some(entry => entry.name === name)) {
result.push(nameAndKind(name, kind));
}
}
}
}
}
/**
* Check all of the declared modules and those in node modules. Possible sources of modules:
* Modules that are found by the type checker
@@ -171,19 +201,10 @@ namespace ts.Completions.PathCompletions {
const fileExtensions = getSupportedExtensionsForModuleResolution(compilerOptions);
if (baseUrl) {
const projectDir = compilerOptions.project || host.getCurrentDirectory();
const absolute = isRootedDiskPath(baseUrl) ? baseUrl : combinePaths(projectDir, baseUrl);
getCompletionEntriesForDirectoryFragment(fragment, normalizePath(absolute), fileExtensions, /*includeExtensions*/ false, host, /*exclude*/ undefined, result);
for (const path in paths!) {
const patterns = paths![path];
if (paths!.hasOwnProperty(path) && patterns) {
for (const { name, kind } of getCompletionsForPathMapping(path, patterns, fragment, baseUrl, fileExtensions, host)) {
// Path mappings may provide a duplicate way to get to something we've already added, so don't add again.
if (!result.some(entry => entry.name === name)) {
result.push(nameAndKind(name, kind));
}
}
}
const absolute = normalizePath(combinePaths(projectDir, baseUrl));
getCompletionEntriesForDirectoryFragment(fragment, absolute, fileExtensions, /*includeExtensions*/ false, host, /*exclude*/ undefined, result);
if (paths) {
addCompletionEntriesFromPaths(result, fragment, absolute, fileExtensions, paths, host);
}
}
@@ -329,7 +350,7 @@ namespace ts.Completions.PathCompletions {
const seen = createMap<true>();
if (options.types) {
for (const typesName of options.types) {
const moduleName = getUnmangledNameForScopedPackage(typesName);
const moduleName = unmangleScopedPackageName(typesName);
pushResult(moduleName);
}
}
@@ -363,7 +384,7 @@ namespace ts.Completions.PathCompletions {
for (let typeDirectory of directories) {
typeDirectory = normalizePath(typeDirectory);
const directoryName = getBaseFileName(typeDirectory);
const moduleName = getUnmangledNameForScopedPackage(directoryName);
const moduleName = unmangleScopedPackageName(directoryName);
pushResult(moduleName);
}
}
@@ -390,6 +411,18 @@ namespace ts.Completions.PathCompletions {
return paths;
}
function findPackageJson(directory: string, host: LanguageServiceHost): string | undefined {
let packageJson: string | undefined;
forEachAncestorDirectory(directory, ancestor => {
if (ancestor === "node_modules") return true;
packageJson = findConfigFile(ancestor, (f) => tryFileExists(host, f), "package.json");
if (packageJson) {
return true; // break out
}
});
return packageJson;
}
function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string): ReadonlyArray<string> {
if (!host.readFile || !host.fileExists) return emptyArray;
+3 -3
View File
@@ -719,7 +719,7 @@ namespace ts.refactor.extractSymbol {
// Make a unique name for the extracted function
const file = scope.getSourceFile();
const functionNameText = getUniqueName(isClassLike(scope) ? "newMethod" : "newFunction", file);
const isJS = isInJavaScriptFile(scope);
const isJS = isInJSFile(scope);
const functionName = createIdentifier(functionNameText);
@@ -1006,7 +1006,7 @@ namespace ts.refactor.extractSymbol {
// Make a unique name for the extracted variable
const file = scope.getSourceFile();
const localNameText = getUniqueName(isClassLike(scope) ? "newProperty" : "newLocal", file);
const isJS = isInJavaScriptFile(scope);
const isJS = isInJSFile(scope);
const variableType = isJS || !checker.isContextSensitive(node)
? undefined
@@ -1424,7 +1424,7 @@ namespace ts.refactor.extractSymbol {
if (expressionDiagnostic) {
constantErrors.push(expressionDiagnostic);
}
if (isClassLike(scope) && isInJavaScriptFile(scope)) {
if (isClassLike(scope) && isInJSFile(scope)) {
constantErrors.push(createDiagnosticForNode(scope, Messages.cannotExtractToJSClass));
}
if (isArrowFunction(scope) && !isBlock(scope.body)) {
@@ -41,7 +41,7 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
const fieldInfo = getConvertibleFieldAtPosition(context);
if (!fieldInfo) return undefined;
const isJS = isSourceFileJavaScript(file);
const isJS = isSourceFileJS(file);
const changeTracker = textChanges.ChangeTracker.fromContext(context);
const { isStatic, isReadonly, fieldName, accessorName, originalName, type, container, declaration, renameAccessor } = fieldInfo;
@@ -226,7 +226,7 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
const { file, program, cancellationToken } = context;
const referenceEntries = mapDefined(FindAllReferences.getReferenceEntriesForNode(originalName.parent.pos, originalName, program, [file], cancellationToken!), entry => // TODO: GH#18217
(entry.type === "node" && rangeContainsRange(constructor, entry.node) && isIdentifier(entry.node) && isWriteAccess(entry.node)) ? entry.node : undefined);
(entry.kind !== FindAllReferences.EntryKind.Span && rangeContainsRange(constructor, entry.node) && isIdentifier(entry.node) && isWriteAccess(entry.node)) ? entry.node : undefined);
forEach(referenceEntries, entry => {
const parent = entry.parent;
+2 -2
View File
@@ -158,7 +158,7 @@ namespace ts.refactor {
const shouldMove = (name: Identifier): boolean => {
const symbol = isBindingElement(name.parent)
? getPropertySymbolFromBindingElement(checker, name.parent as BindingElement & { name: Identifier })
? getPropertySymbolFromBindingElement(checker, name.parent as ObjectBindingElementWithoutPropertyName)
: skipAlias(checker.getSymbolAtLocation(name)!, checker); // TODO: GH#18217
return !!symbol && movedSymbols.has(symbol);
};
@@ -657,7 +657,7 @@ namespace ts.refactor {
case SyntaxKind.ExpressionStatement: {
const { expression } = statement as ExpressionStatement;
return isBinaryExpression(expression) && getSpecialPropertyAssignmentKind(expression) === SpecialPropertyAssignmentKind.ExportsProperty
return isBinaryExpression(expression) && getAssignmentDeclarationKind(expression) === AssignmentDeclarationKind.ExportsProperty
? cb(statement as TopLevelExpressionStatement)
: undefined;
}
+31 -15
View File
@@ -25,8 +25,9 @@ namespace ts.Rename {
return undefined;
}
// Can't rename a module name.
if (isStringLiteralLike(node) && tryGetImportFromModuleSpecifier(node)) return undefined;
if (isStringLiteralLike(node) && tryGetImportFromModuleSpecifier(node)) {
return getRenameInfoForModule(node, sourceFile, symbol);
}
const kind = SymbolDisplay.getSymbolKind(typeChecker, symbol, node);
const specifierName = (isImportOrExportSpecifierName(node) || isStringOrNumericLiteralLike(node) && node.parent.kind === SyntaxKind.ComputedPropertyName)
@@ -37,29 +38,44 @@ namespace ts.Rename {
return getRenameInfoSuccess(displayName, fullDisplayName, kind, SymbolDisplay.getSymbolModifiers(symbol), node, sourceFile);
}
function getRenameInfoSuccess(displayName: string, fullDisplayName: string, kind: ScriptElementKind, kindModifiers: string, node: Node, sourceFile: SourceFile): RenameInfo {
function getRenameInfoForModule(node: StringLiteralLike, sourceFile: SourceFile, moduleSymbol: Symbol): RenameInfo | undefined {
if (!isExternalModuleNameRelative(node.text)) {
return getRenameInfoError(Diagnostics.You_cannot_rename_a_module_via_a_global_import);
}
const moduleSourceFile = find(moduleSymbol.declarations, isSourceFile);
if (!moduleSourceFile) return undefined;
const withoutIndex = node.text.endsWith("/index") || node.text.endsWith("/index.js") ? undefined : tryRemoveSuffix(removeFileExtension(moduleSourceFile.fileName), "/index");
const name = withoutIndex === undefined ? moduleSourceFile.fileName : withoutIndex;
const kind = withoutIndex === undefined ? ScriptElementKind.moduleElement : ScriptElementKind.directory;
const indexAfterLastSlash = node.text.lastIndexOf("/") + 1;
// Span should only be the last component of the path. + 1 to account for the quote character.
const triggerSpan = createTextSpan(node.getStart(sourceFile) + 1 + indexAfterLastSlash, node.text.length - indexAfterLastSlash);
return {
canRename: true,
fileToRename: name,
kind,
displayName: name,
fullDisplayName: name,
kindModifiers: ScriptElementKindModifier.none,
triggerSpan,
};
}
function getRenameInfoSuccess(displayName: string, fullDisplayName: string, kind: ScriptElementKind, kindModifiers: string, node: Node, sourceFile: SourceFile): RenameInfoSuccess {
return {
canRename: true,
fileToRename: undefined,
kind,
displayName,
localizedErrorMessage: undefined,
fullDisplayName,
kindModifiers,
triggerSpan: createTriggerSpanForNode(node, sourceFile)
};
}
function getRenameInfoError(diagnostic: DiagnosticMessage): RenameInfo {
// TODO: GH#18217
return {
canRename: false,
localizedErrorMessage: getLocaleSpecificMessage(diagnostic),
displayName: undefined!,
fullDisplayName: undefined!,
kind: undefined!,
kindModifiers: undefined!,
triggerSpan: undefined!
};
function getRenameInfoError(diagnostic: DiagnosticMessage): RenameInfoFailure {
return { canRename: false, localizedErrorMessage: getLocaleSpecificMessage(diagnostic) };
}
function createTriggerSpanForNode(node: Node, sourceFile: SourceFile) {
+37 -20
View File
@@ -541,6 +541,7 @@ namespace ts {
public fileName: string;
public path: Path;
public resolvedPath: Path;
public originalFileName: string;
public text: string;
public scriptSnapshot: IScriptSnapshot;
public lineMap: ReadonlyArray<number>;
@@ -760,7 +761,7 @@ namespace ts {
break;
case SyntaxKind.BinaryExpression:
if (getSpecialPropertyAssignmentKind(node as BinaryExpression) !== SpecialPropertyAssignmentKind.None) {
if (getAssignmentDeclarationKind(node as BinaryExpression) !== AssignmentDeclarationKind.None) {
addDeclaration(node as BinaryExpression);
}
// falls through
@@ -1175,9 +1176,10 @@ namespace ts {
const rootFileNames = hostCache.getRootFileNames();
const hasInvalidatedResolution: HasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse;
const projectReferences = hostCache.getProjectReferences();
// If the program is already up-to-date, we can reuse it
if (isProgramUptoDate(program, rootFileNames, hostCache.compilationSettings(), path => hostCache!.getVersion(path), fileExists, hasInvalidatedResolution, !!host.hasChangedAutomaticTypeDirectiveNames)) {
if (isProgramUptoDate(program, rootFileNames, hostCache.compilationSettings(), path => hostCache!.getVersion(path), fileExists, hasInvalidatedResolution, !!host.hasChangedAutomaticTypeDirectiveNames, projectReferences)) {
return;
}
@@ -1217,6 +1219,10 @@ namespace ts {
getDirectories: path => {
return host.getDirectories ? host.getDirectories(path) : [];
},
readDirectory(path, extensions, exclude, include, depth) {
Debug.assertDefined(host.readDirectory, "'LanguageServiceHost.readDirectory' must be implemented to correctly process 'projectReferences'");
return host.readDirectory!(path, extensions, exclude, include, depth);
},
onReleaseOldSourceFile,
hasInvalidatedResolution,
hasChangedAutomaticTypeDirectiveNames: host.hasChangedAutomaticTypeDirectiveNames
@@ -1240,7 +1246,7 @@ namespace ts {
options: newSettings,
host: compilerHost,
oldProgram: program,
projectReferences: hostCache.getProjectReferences()
projectReferences
};
program = createProgram(options);
@@ -1377,7 +1383,7 @@ namespace ts {
// Therefore only get diagnostics for given file.
const semanticDiagnostics = program.getSemanticDiagnostics(targetSourceFile, cancellationToken);
if (!program.getCompilerOptions().declaration) {
if (!getEmitDeclarations(program.getCompilerOptions())) {
return semanticDiagnostics.slice();
}
@@ -1538,20 +1544,20 @@ namespace ts {
const node = getTouchingPropertyName(sourceFile, position);
if (isIdentifier(node) && isJsxOpeningElement(node.parent) || isJsxClosingElement(node.parent)) {
const { openingElement, closingElement } = node.parent.parent;
return [openingElement, closingElement].map((node): RenameLocation => ({ fileName: sourceFile.fileName, textSpan: createTextSpanFromNode(node.tagName, sourceFile) }));
return [openingElement, closingElement].map((node): RenameLocation =>
({ fileName: sourceFile.fileName, textSpan: createTextSpanFromNode(node.tagName, sourceFile) }));
}
else {
const refs = getReferences(node, position, { findInStrings, findInComments, isForRename: true });
return refs && refs.map(({ fileName, textSpan }): RenameLocation => ({ fileName, textSpan }));
return getReferencesWorker(node, position, { findInStrings, findInComments, isForRename: true }, FindAllReferences.toRenameLocation);
}
}
function getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined {
synchronizeHostData();
return getReferences(getTouchingPropertyName(getValidSourceFile(fileName), position), position);
return getReferencesWorker(getTouchingPropertyName(getValidSourceFile(fileName), position), position, {}, FindAllReferences.toReferenceEntry);
}
function getReferences(node: Node, position: number, options?: FindAllReferences.Options): ReferenceEntry[] | undefined {
function getReferencesWorker<T>(node: Node, position: number, options: FindAllReferences.Options, cb: FindAllReferences.ToReferenceOrRenameEntry<T>): T[] | undefined {
synchronizeHostData();
// Exclude default library when renaming as commonly user don't want to change that file.
@@ -1559,7 +1565,7 @@ namespace ts {
? program.getSourceFiles().filter(sourceFile => !program.isSourceFileDefaultLibrary(sourceFile))
: program.getSourceFiles();
return FindAllReferences.findReferencedEntries(program, cancellationToken, sourceFiles, node, position, options);
return FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, sourceFiles, node, position, options, cb);
}
function findReferences(fileName: string, position: number): ReferencedSymbol[] | undefined {
@@ -1803,25 +1809,36 @@ namespace ts {
return ts.getEditsForFileRename(getProgram()!, oldFilePath, newFilePath, host, formatting.getFormatContext(formatOptions), preferences, sourceMapper);
}
function applyCodeActionCommand(action: CodeActionCommand): Promise<ApplyCodeActionCommandResult>;
function applyCodeActionCommand(action: CodeActionCommand[]): Promise<ApplyCodeActionCommandResult[]>;
function applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise<ApplyCodeActionCommandResult | ApplyCodeActionCommandResult[]>;
function applyCodeActionCommand(action: CodeActionCommand, formatSettings?: FormatCodeSettings): Promise<ApplyCodeActionCommandResult>;
function applyCodeActionCommand(action: CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise<ApplyCodeActionCommandResult[]>;
function applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise<ApplyCodeActionCommandResult | ApplyCodeActionCommandResult[]>;
function applyCodeActionCommand(fileName: Path, action: CodeActionCommand): Promise<ApplyCodeActionCommandResult>;
function applyCodeActionCommand(fileName: Path, action: CodeActionCommand[]): Promise<ApplyCodeActionCommandResult[]>;
function applyCodeActionCommand(fileName: Path | CodeActionCommand | CodeActionCommand[], actionOrUndefined?: CodeActionCommand | CodeActionCommand[]): Promise<ApplyCodeActionCommandResult | ApplyCodeActionCommandResult[]> {
const action = typeof fileName === "string" ? actionOrUndefined! : fileName as CodeActionCommand[];
return isArray(action) ? Promise.all(action.map(applySingleCodeActionCommand)) : applySingleCodeActionCommand(action);
function applyCodeActionCommand(fileName: Path | CodeActionCommand | CodeActionCommand[], actionOrFormatSettingsOrUndefined?: CodeActionCommand | CodeActionCommand[] | FormatCodeSettings): Promise<ApplyCodeActionCommandResult | ApplyCodeActionCommandResult[]> {
const action = typeof fileName === "string" ? actionOrFormatSettingsOrUndefined as CodeActionCommand | CodeActionCommand[] : fileName as CodeActionCommand[];
const formatSettings = typeof fileName !== "string" ? actionOrFormatSettingsOrUndefined as FormatCodeSettings : undefined;
return isArray(action) ? Promise.all(action.map(a => applySingleCodeActionCommand(a, formatSettings))) : applySingleCodeActionCommand(action, formatSettings);
}
function applySingleCodeActionCommand(action: CodeActionCommand): Promise<ApplyCodeActionCommandResult> {
function applySingleCodeActionCommand(action: CodeActionCommand, formatSettings: FormatCodeSettings | undefined): Promise<ApplyCodeActionCommandResult> {
const getPath = (path: string): Path => toPath(path, currentDirectory, getCanonicalFileName);
switch (action.type) {
case "install package":
return host.installPackage
? host.installPackage({ fileName: toPath(action.file, currentDirectory, getCanonicalFileName), packageName: action.packageName })
? host.installPackage({ fileName: getPath(action.file), packageName: action.packageName })
: Promise.reject("Host does not implement `installPackage`");
case "generate types": {
const { fileToGenerateTypesFor, outputFileName } = action;
if (!host.inspectValue) return Promise.reject("Host does not implement `installPackage`");
const valueInfoPromise = host.inspectValue({ fileNameToRequire: fileToGenerateTypesFor });
return valueInfoPromise.then(valueInfo => {
const fullOut = getPath(outputFileName);
host.writeFile!(fullOut, valueInfoToDeclarationFileText(valueInfo, formatSettings || testFormatSettings)); // TODO: GH#18217
return { successMessage: `Wrote types to '${fullOut}'` };
});
}
default:
return Debug.fail();
// TODO: Debug.assertNever(action); will only work if there is more than one type.
return Debug.assertNever(action);
}
}
+6 -5
View File
@@ -38,7 +38,8 @@ namespace ts.SignatureHelp {
return undefined;
}
const argumentInfo = getContainingArgumentInfo(startingToken, position, sourceFile, typeChecker);
const isManuallyInvoked = !!triggerReason && triggerReason.kind === "invoked";
const argumentInfo = getContainingArgumentInfo(startingToken, position, sourceFile, typeChecker, isManuallyInvoked);
if (!argumentInfo) return undefined;
cancellationToken.throwIfCancellationRequested();
@@ -50,7 +51,7 @@ namespace ts.SignatureHelp {
if (!candidateInfo) {
// We didn't have any sig help items produced by the TS compiler. If this is a JS
// file, then see if we can figure out anything better.
return isSourceFileJavaScript(sourceFile) ? createJavaScriptSignatureHelpItems(argumentInfo, program, cancellationToken) : undefined;
return isSourceFileJS(sourceFile) ? createJSSignatureHelpItems(argumentInfo, program, cancellationToken) : undefined;
}
return typeChecker.runWithCancellationToken(cancellationToken, typeChecker =>
@@ -115,7 +116,7 @@ namespace ts.SignatureHelp {
}
}
function createJavaScriptSignatureHelpItems(argumentInfo: ArgumentListInfo, program: Program, cancellationToken: CancellationToken): SignatureHelpItems | undefined {
function createJSSignatureHelpItems(argumentInfo: ArgumentListInfo, program: Program, cancellationToken: CancellationToken): SignatureHelpItems | undefined {
if (argumentInfo.invocation.kind === InvocationKind.Contextual) return undefined;
// See if we can find some symbol with the call expression name that has call signatures.
const expression = getExpressionFromInvocation(argumentInfo.invocation);
@@ -450,8 +451,8 @@ namespace ts.SignatureHelp {
return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart);
}
function getContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile, checker: TypeChecker): ArgumentListInfo | undefined {
for (let n = node; !isBlock(n) && !isSourceFile(n); n = n.parent) {
function getContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile, checker: TypeChecker, isManuallyInvoked: boolean): ArgumentListInfo | undefined {
for (let n = node; isManuallyInvoked || (!isBlock(n) && !isSourceFile(n)); n = n.parent) {
// If the node is not a subspan of its parent, this is a big problem.
// There have been crashes that might be caused by this violation.
Debug.assert(rangeContainsRange(n.parent, n), "Not a subspan", () => `Child: ${Debug.showSyntaxKind(n)}, parent: ${Debug.showSyntaxKind(n.parent)}`);
+44 -19
View File
@@ -11,7 +11,7 @@ namespace ts {
diags.push(createDiagnosticForNode(getErrorNodeFromCommonJsIndicator(sourceFile.commonJsModuleIndicator), Diagnostics.File_is_a_CommonJS_module_it_may_be_converted_to_an_ES6_module));
}
const isJsFile = isSourceFileJavaScript(sourceFile);
const isJsFile = isSourceFileJS(sourceFile);
check(sourceFile);
@@ -36,7 +36,7 @@ namespace ts {
if (isJsFile) {
switch (node.kind) {
case SyntaxKind.FunctionExpression:
const decl = getDeclarationOfJSInitializer(node);
const decl = getDeclarationOfExpando(node);
if (decl) {
const symbol = decl.symbol;
if (symbol && (symbol.exports && symbol.exports.size || symbol.members && symbol.members.size)) {
@@ -82,12 +82,12 @@ namespace ts {
switch (statement.kind) {
case SyntaxKind.VariableStatement:
return (statement as VariableStatement).declarationList.declarations.some(decl =>
isRequireCall(propertyAccessLeftHandSide(decl.initializer!), /*checkArgumentIsStringLiteralLike*/ true)); // TODO: GH#18217
!!decl.initializer && isRequireCall(propertyAccessLeftHandSide(decl.initializer), /*checkArgumentIsStringLiteralLike*/ true));
case SyntaxKind.ExpressionStatement: {
const { expression } = statement as ExpressionStatement;
if (!isBinaryExpression(expression)) return isRequireCall(expression, /*checkArgumentIsStringLiteralLike*/ true);
const kind = getSpecialPropertyAssignmentKind(expression);
return kind === SpecialPropertyAssignmentKind.ExportsProperty || kind === SpecialPropertyAssignmentKind.ModuleExports;
const kind = getAssignmentDeclarationKind(expression);
return kind === AssignmentDeclarationKind.ExportsProperty || kind === AssignmentDeclarationKind.ModuleExports;
}
default:
return false;
@@ -132,7 +132,7 @@ namespace ts {
// check that a property access expression exists in there and that it is a handler
const returnStatements = getReturnStatementsWithPromiseHandlers(node);
if (returnStatements.length > 0) {
diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_may_be_converted_to_an_async_function));
diags.push(createDiagnosticForNode(!node.name && isVariableDeclaration(node.parent) && isIdentifier(node.parent.name) ? node.parent.name : node, Diagnostics.This_may_be_converted_to_an_async_function));
}
}
@@ -141,8 +141,8 @@ namespace ts {
}
/** @internal */
export function getReturnStatementsWithPromiseHandlers(node: Node): Node[] {
const returnStatements: Node[] = [];
export function getReturnStatementsWithPromiseHandlers(node: Node): ReturnStatement[] {
const returnStatements: ReturnStatement[] = [];
if (isFunctionLike(node)) {
forEachChild(node, visit);
}
@@ -155,14 +155,8 @@ namespace ts {
return;
}
if (isReturnStatement(child)) {
forEachChild(child, addHandlers);
}
function addHandlers(returnChild: Node) {
if (isPromiseHandler(returnChild)) {
returnStatements.push(child as ReturnStatement);
}
if (isReturnStatement(child) && child.expression && isFixablePromiseHandler(child.expression)) {
returnStatements.push(child);
}
forEachChild(child, visit);
@@ -170,8 +164,39 @@ namespace ts {
return returnStatements;
}
function isPromiseHandler(node: Node): boolean {
return (isCallExpression(node) && isPropertyAccessExpression(node.expression) &&
(node.expression.name.text === "then" || node.expression.name.text === "catch"));
// Should be kept up to date with transformExpression in convertToAsyncFunction.ts
function isFixablePromiseHandler(node: Node): boolean {
// ensure outermost call exists and is a promise handler
if (!isPromiseHandler(node) || !node.arguments.every(isFixablePromiseArgument)) {
return false;
}
// ensure all chained calls are valid
let currentNode = node.expression;
while (isPromiseHandler(currentNode) || isPropertyAccessExpression(currentNode)) {
if (isCallExpression(currentNode) && !currentNode.arguments.every(isFixablePromiseArgument)) {
return false;
}
currentNode = currentNode.expression;
}
return true;
}
function isPromiseHandler(node: Node): node is CallExpression {
return isCallExpression(node) && (hasPropertyAccessExpressionWithName(node, "then") || hasPropertyAccessExpressionWithName(node, "catch"));
}
// should be kept up to date with getTransformationBody in convertToAsyncFunction.ts
function isFixablePromiseArgument(arg: Expression): boolean {
switch (arg.kind) {
case SyntaxKind.NullKeyword:
case SyntaxKind.Identifier: // identifier includes undefined
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
return true;
default:
return false;
}
}
}
+1 -1
View File
@@ -534,7 +534,7 @@ namespace ts.SymbolDisplay {
tags = tagsFromAlias;
}
return { displayParts, documentation, symbolKind, tags: tags! };
return { displayParts, documentation, symbolKind, tags: tags!.length === 0 ? undefined : tags };
function getPrinter() {
if (!printer) {
+102 -24
View File
@@ -209,10 +209,16 @@ namespace ts.textChanges {
export type TypeAnnotatable = SignatureDeclaration | VariableDeclaration | ParameterDeclaration | PropertyDeclaration | PropertySignature;
interface JSDocParameter {
declaration: ParameterDeclaration;
typeNode: TypeNode;
isOptional?: boolean;
}
export class ChangeTracker {
private readonly changes: Change[] = [];
private readonly newFiles: { readonly oldFile: SourceFile, readonly fileName: string, readonly statements: ReadonlyArray<Statement> }[] = [];
private readonly classesWithNodesInsertedAtStart = createMap<ClassDeclaration>(); // Set<ClassDeclaration> implemented as Map<node id, ClassDeclaration>
private readonly newFiles: { readonly oldFile: SourceFile | undefined, readonly fileName: string, readonly statements: ReadonlyArray<Statement> }[] = [];
private readonly classesWithNodesInsertedAtStart = createMap<{ readonly node: ClassDeclaration | InterfaceDeclaration | ObjectLiteralExpression, readonly sourceFile: SourceFile }>(); // Set<ClassDeclaration> implemented as Map<node id, ClassDeclaration>
private readonly deletedNodes: { readonly sourceFile: SourceFile, readonly node: Node | NodeArray<TypeParameterDeclaration> }[] = [];
public static fromContext(context: TextChangesContext): ChangeTracker {
@@ -295,7 +301,7 @@ namespace ts.textChanges {
}
private insertNodesAt(sourceFile: SourceFile, pos: number, newNodes: ReadonlyArray<Node>, options: ReplaceWithMultipleNodesOptions = {}): void {
this.changes.push({ kind: ChangeKind.ReplaceWithMultipleNodes, sourceFile, options, nodes: newNodes, range: { pos, end: pos } });
this.replaceRangeWithNodes(sourceFile, createRange(pos), newNodes, options);
}
public insertNodeAtTopOfFile(sourceFile: SourceFile, newNode: Statement, blankLineBetween: boolean): void {
@@ -312,7 +318,17 @@ namespace ts.textChanges {
public insertModifierBefore(sourceFile: SourceFile, modifier: SyntaxKind, before: Node): void {
const pos = before.getStart(sourceFile);
this.replaceRange(sourceFile, { pos, end: pos }, createToken(modifier), { suffix: " " });
this.insertNodeAt(sourceFile, pos, createToken(modifier), { suffix: " " });
}
public insertLastModifierBefore(sourceFile: SourceFile, modifier: SyntaxKind, before: Node): void {
if (!before.modifiers) {
this.insertModifierBefore(sourceFile, modifier, before);
return;
}
const pos = before.modifiers.end;
this.insertNodeAt(sourceFile, pos, createToken(modifier), { prefix: " " });
}
public insertCommentBeforeLine(sourceFile: SourceFile, lineNumber: number, position: number, commentText: string): void {
@@ -329,6 +345,12 @@ namespace ts.textChanges {
this.insertText(sourceFile, token.getStart(sourceFile), text);
}
public insertCommentThenNewline(sourceFile: SourceFile, character: number, position: number, commentText: string): void {
const token = getTouchingToken(sourceFile, position);
const text = "/**" + commentText + "*/" + this.newLineCharacter + repeatString(" ", character);
this.insertText(sourceFile, token.getStart(sourceFile), text);
}
public replaceRangeWithText(sourceFile: SourceFile, range: TextRange, text: string) {
this.changes.push({ kind: ChangeKind.Text, sourceFile, range, text });
}
@@ -337,6 +359,23 @@ namespace ts.textChanges {
this.replaceRangeWithText(sourceFile, createRange(pos), text);
}
public tryInsertJSDocParameters(sourceFile: SourceFile, parameters: JSDocParameter[]) {
if (parameters.length === 0) {
return;
}
const parent = parameters[0].declaration.parent;
const indent = getLineAndCharacterOfPosition(sourceFile, parent.getStart()).character;
let commentText = "\n";
for (const { declaration, typeNode, isOptional } of parameters) {
if (isIdentifier(declaration.name)) {
const printed = changesToText.getNonformattedText(typeNode, sourceFile, this.newLineCharacter).text;
commentText += this.printJSDocParameter(indent, printed, declaration.name, isOptional);
}
}
commentText += repeatString(" ", indent + 1);
this.insertCommentThenNewline(sourceFile, indent, parent.getStart(), commentText);
}
/** Prefer this over replacing a node with another that has a type annotation, as it avoids reformatting the other parts of the node. */
public tryInsertTypeAnnotation(sourceFile: SourceFile, node: TypeAnnotatable, type: TypeNode): void {
let endNode: Node | undefined;
@@ -355,6 +394,27 @@ namespace ts.textChanges {
this.insertNodeAt(sourceFile, endNode.end, type, { prefix: ": " });
}
public tryInsertJSDocType(sourceFile: SourceFile, node: Node, type: TypeNode): void {
const printed = changesToText.getNonformattedText(type, sourceFile, this.newLineCharacter).text;
let commentText;
if (isGetAccessorDeclaration(node)) {
commentText = ` @return {${printed}} `;
}
else {
commentText = ` @type {${printed}} `;
node = node.parent;
}
this.insertCommentThenNewline(sourceFile, getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile)).character, node.getStart(sourceFile), commentText);
}
private printJSDocParameter(indent: number, printed: string, name: Identifier, isOptionalParameter: boolean | undefined) {
let printName = unescapeLeadingUnderscores(name.escapedText);
if (isOptionalParameter) {
printName = `[${printName}]`;
}
return repeatString(" ", indent) + ` * @param {${printed}} ${printName}\n`;
}
public insertTypeParameters(sourceFile: SourceFile, node: SignatureDeclaration, typeParameters: ReadonlyArray<TypeParameterDeclaration>): void {
// If no `(`, is an arrow function `x => x`, so use the pos of the first parameter
const start = (findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile) || first(node.parameters)).getStart(sourceFile);
@@ -403,32 +463,40 @@ namespace ts.textChanges {
public insertNodeAtEndOfScope(sourceFile: SourceFile, scope: Node, newNode: Node): void {
const pos = getAdjustedStartPosition(sourceFile, scope.getLastToken()!, {}, Position.Start);
this.replaceRange(sourceFile, { pos, end: pos }, newNode, {
this.insertNodeAt(sourceFile, pos, newNode, {
prefix: isLineBreak(sourceFile.text.charCodeAt(scope.getLastToken()!.pos)) ? this.newLineCharacter : this.newLineCharacter + this.newLineCharacter,
suffix: this.newLineCharacter
});
}
public insertNodeAtClassStart(sourceFile: SourceFile, cls: ClassLikeDeclaration | InterfaceDeclaration, newElement: ClassElement): void {
this.insertNodeAtStartWorker(sourceFile, cls, newElement);
}
public insertNodeAtObjectStart(sourceFile: SourceFile, obj: ObjectLiteralExpression, newElement: ObjectLiteralElementLike): void {
this.insertNodeAtStartWorker(sourceFile, obj, newElement);
}
private insertNodeAtStartWorker(sourceFile: SourceFile, cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression, newElement: ClassElement | ObjectLiteralElementLike): void {
const clsStart = cls.getStart(sourceFile);
const indentation = formatting.SmartIndenter.findFirstNonWhitespaceColumn(getLineStartPositionForPosition(clsStart, sourceFile), clsStart, sourceFile, this.formatContext.options)
+ this.formatContext.options.indentSize!;
this.insertNodeAt(sourceFile, cls.members.pos, newElement, { indentation, ...this.getInsertNodeAtClassStartPrefixSuffix(sourceFile, cls) });
this.insertNodeAt(sourceFile, getMembersOrProperties(cls).pos, newElement, { indentation, ...this.getInsertNodeAtStartPrefixSuffix(sourceFile, cls) });
}
private getInsertNodeAtClassStartPrefixSuffix(sourceFile: SourceFile, cls: ClassLikeDeclaration | InterfaceDeclaration): { prefix: string, suffix: string } {
if (cls.members.length === 0) {
if (addToSeen(this.classesWithNodesInsertedAtStart, getNodeId(cls), cls)) {
private getInsertNodeAtStartPrefixSuffix(sourceFile: SourceFile, cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression): { prefix: string, suffix: string } {
const comma = isObjectLiteralExpression(cls) ? "," : "";
if (getMembersOrProperties(cls).length === 0) {
if (addToSeen(this.classesWithNodesInsertedAtStart, getNodeId(cls), { node: cls, sourceFile })) {
// For `class C {\n}`, don't add the trailing "\n"
const shouldSuffix = (positionsAreOnSameLine as any)(...getClassBraceEnds(cls, sourceFile), sourceFile); // TODO: GH#4130 remove 'as any'
return { prefix: this.newLineCharacter, suffix: shouldSuffix ? this.newLineCharacter : "" };
const shouldSuffix = (positionsAreOnSameLine as any)(...getClassOrObjectBraceEnds(cls, sourceFile), sourceFile); // TODO: GH#4130 remove 'as any'
return { prefix: this.newLineCharacter, suffix: comma + (shouldSuffix ? this.newLineCharacter : "") };
}
else {
return { prefix: "", suffix: this.newLineCharacter };
return { prefix: "", suffix: comma + this.newLineCharacter };
}
}
else {
return { prefix: this.newLineCharacter, suffix: "" };
return { prefix: this.newLineCharacter, suffix: comma };
}
}
@@ -647,9 +715,8 @@ namespace ts.textChanges {
}
private finishClassesWithNodesInsertedAtStart(): void {
this.classesWithNodesInsertedAtStart.forEach(cls => {
const sourceFile = cls.getSourceFile();
const [openBraceEnd, closeBraceEnd] = getClassBraceEnds(cls, sourceFile);
this.classesWithNodesInsertedAtStart.forEach(({ node, sourceFile }) => {
const [openBraceEnd, closeBraceEnd] = getClassOrObjectBraceEnds(node, sourceFile);
// For `class C { }` remove the whitespace inside the braces.
if (positionsAreOnSameLine(openBraceEnd, closeBraceEnd, sourceFile) && openBraceEnd !== closeBraceEnd - 1) {
this.deleteRange(sourceFile, createRange(openBraceEnd, closeBraceEnd - 1));
@@ -698,7 +765,7 @@ namespace ts.textChanges {
return changes;
}
public createNewFile(oldFile: SourceFile, fileName: string, statements: ReadonlyArray<Statement>) {
public createNewFile(oldFile: SourceFile | undefined, fileName: string, statements: ReadonlyArray<Statement>) {
this.newFiles.push({ oldFile, fileName, statements });
}
}
@@ -708,12 +775,19 @@ namespace ts.textChanges {
return skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, {}, Position.FullStart), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true);
}
function getClassBraceEnds(cls: ClassLikeDeclaration | InterfaceDeclaration, sourceFile: SourceFile): [number, number] {
function getClassOrObjectBraceEnds(cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression, sourceFile: SourceFile): [number, number] {
return [findChildOfKind(cls, SyntaxKind.OpenBraceToken, sourceFile)!.end, findChildOfKind(cls, SyntaxKind.CloseBraceToken, sourceFile)!.end];
}
function getMembersOrProperties(cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression): NodeArray<Node> {
return isObjectLiteralExpression(cls) ? cls.properties : cls.members;
}
export type ValidateNonFormattedText = (node: Node, text: string) => void;
export function getNewFileText(statements: ReadonlyArray<Statement>, scriptKind: ScriptKind, newLineCharacter: string, formatContext: formatting.FormatContext): string {
return changesToText.newFileChangesWorker(/*oldFile*/ undefined, scriptKind, statements, newLineCharacter, formatContext);
}
namespace changesToText {
export function getTextChangesFromChanges(changes: ReadonlyArray<Change>, newLineCharacter: string, formatContext: formatting.FormatContext, validate: ValidateNonFormattedText | undefined): FileTextChanges[] {
return group(changes, c => c.sourceFile.path).map(changesInFile => {
@@ -732,13 +806,17 @@ namespace ts.textChanges {
});
}
export function newFileChanges(oldFile: SourceFile, fileName: string, statements: ReadonlyArray<Statement>, newLineCharacter: string, formatContext: formatting.FormatContext): FileTextChanges {
export function newFileChanges(oldFile: SourceFile | undefined, fileName: string, statements: ReadonlyArray<Statement>, newLineCharacter: string, formatContext: formatting.FormatContext): FileTextChanges {
const text = newFileChangesWorker(oldFile, getScriptKindFromFileName(fileName), statements, newLineCharacter, formatContext);
return { fileName, textChanges: [createTextChange(createTextSpan(0, 0), text)], isNewFile: true };
}
export function newFileChangesWorker(oldFile: SourceFile | undefined, scriptKind: ScriptKind, statements: ReadonlyArray<Statement>, newLineCharacter: string, formatContext: formatting.FormatContext): string {
// TODO: this emits the file, parses it back, then formats it that -- may be a less roundabout way to do this
const nonFormattedText = statements.map(s => getNonformattedText(s, oldFile, newLineCharacter).text).join(newLineCharacter);
const sourceFile = createSourceFile(fileName, nonFormattedText, ScriptTarget.ESNext, /*setParentNodes*/ true);
const sourceFile = createSourceFile("any file name", nonFormattedText, ScriptTarget.ESNext, /*setParentNodes*/ true, scriptKind);
const changes = formatting.formatDocument(sourceFile, formatContext);
const text = applyChanges(nonFormattedText, changes);
return { fileName, textChanges: [createTextChange(createTextSpan(0, 0), text)], isNewFile: true };
return applyChanges(nonFormattedText, changes);
}
function computeNewText(change: Change, sourceFile: SourceFile, newLineCharacter: string, formatContext: formatting.FormatContext, validate: ValidateNonFormattedText | undefined): string {
@@ -778,10 +856,10 @@ namespace ts.textChanges {
}
/** Note: output node may be mutated input node. */
function getNonformattedText(node: Node, sourceFile: SourceFile | undefined, newLineCharacter: string): { text: string, node: Node } {
export function getNonformattedText(node: Node, sourceFile: SourceFile | undefined, newLineCharacter: string): { text: string, node: Node } {
const writer = new Writer(newLineCharacter);
const newLine = newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed;
createPrinter({ newLine }, writer).writeNode(EmitHint.Unspecified, node, sourceFile, writer);
createPrinter({ newLine, neverAsciiEscape: true }, writer).writeNode(EmitHint.Unspecified, node, sourceFile, writer);
return { text: writer.getText(), node: assignPositionsToNode(node) };
}
}
+1
View File
@@ -48,6 +48,7 @@ namespace ts {
options.paths = undefined;
options.rootDirs = undefined;
options.declaration = undefined;
options.composite = undefined;
options.declarationDir = undefined;
options.out = undefined;
options.outFile = undefined;
+2 -1
View File
@@ -69,6 +69,7 @@
"codefixes/inferFromUsage.ts",
"codefixes/fixInvalidImportSyntax.ts",
"codefixes/fixStrictClassInitialization.ts",
"codefixes/generateTypes.ts",
"codefixes/requireInTs.ts",
"codefixes/useDefaultImport.ts",
"codefixes/fixAddModuleReferTypeMissingTypeof.ts",
@@ -82,6 +83,6 @@
"services.ts",
"breakpoints.ts",
"transform.ts",
"shims.ts"
"shims.ts",
]
}
+58 -10
View File
@@ -231,6 +231,8 @@ namespace ts {
isKnownTypesPackageName?(name: string): boolean;
installPackage?(options: InstallPackageOptions): Promise<ApplyCodeActionCommandResult>;
/* @internal */ inspectValue?(options: InspectValueOptions): Promise<ValueInfo>;
writeFile?(fileName: string, content: string): void;
}
/* @internal */
@@ -331,9 +333,9 @@ namespace ts {
getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray<number>, formatOptions: FormatCodeSettings, preferences: UserPreferences): ReadonlyArray<CodeFixAction>;
getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings, preferences: UserPreferences): CombinedCodeActions;
applyCodeActionCommand(action: CodeActionCommand): Promise<ApplyCodeActionCommandResult>;
applyCodeActionCommand(action: CodeActionCommand[]): Promise<ApplyCodeActionCommandResult[]>;
applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise<ApplyCodeActionCommandResult | ApplyCodeActionCommandResult[]>;
applyCodeActionCommand(action: CodeActionCommand, formatSettings?: FormatCodeSettings): Promise<ApplyCodeActionCommandResult>;
applyCodeActionCommand(action: CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise<ApplyCodeActionCommandResult[]>;
applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise<ApplyCodeActionCommandResult | ApplyCodeActionCommandResult[]>;
/** @deprecated `fileName` will be ignored */
applyCodeActionCommand(fileName: string, action: CodeActionCommand): Promise<ApplyCodeActionCommandResult>;
/** @deprecated `fileName` will be ignored */
@@ -526,12 +528,22 @@ namespace ts {
// Publicly, this type is just `{}`. Internally it is a union of all the actions we use.
// See `commands?: {}[]` in protocol.ts
export type CodeActionCommand = InstallPackageAction;
export type CodeActionCommand = InstallPackageAction | GenerateTypesAction;
export interface InstallPackageAction {
/* @internal */ file: string;
/* @internal */ type: "install package";
/* @internal */ packageName: string;
/* @internal */ readonly type: "install package";
/* @internal */ readonly file: string;
/* @internal */ readonly packageName: string;
}
export interface GenerateTypesAction extends GenerateTypesOptions {
/* @internal */ readonly type: "generate types";
}
export interface GenerateTypesOptions {
readonly file: string; // File that was importing fileToGenerateTypesFor; used for formatting options.
readonly fileToGenerateTypesFor: string;
readonly outputFileName: string;
}
/**
@@ -607,6 +619,8 @@ namespace ts {
}
export interface RenameLocation extends DocumentSpan {
readonly prefixText?: string;
readonly suffixText?: string;
}
export interface ReferenceEntry extends DocumentSpan {
@@ -717,6 +731,31 @@ namespace ts {
indentMultiLineObjectLiteralBeginningOnBlankLine?: boolean;
}
/* @internal */
export const testFormatSettings: FormatCodeSettings = {
baseIndentSize: 0,
indentSize: 4,
tabSize: 4,
newLineCharacter: "\n",
convertTabsToSpaces: true,
indentStyle: IndentStyle.Smart,
insertSpaceAfterCommaDelimiter: true,
insertSpaceAfterSemicolonInForStatements: true,
insertSpaceBeforeAndAfterBinaryOperators: true,
insertSpaceAfterConstructor: false,
insertSpaceAfterKeywordsInControlFlowStatements: true,
insertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
insertSpaceAfterTypeAssertion: false,
placeOpenBraceOnNewLineForFunctions: false,
placeOpenBraceOnNewLineForControlBlocks: false,
insertSpaceBeforeTypeAnnotation: false
};
export interface DefinitionInfo extends DocumentSpan {
kind: ScriptElementKind;
name: string;
@@ -782,15 +821,24 @@ namespace ts {
tags?: JSDocTagInfo[];
}
export interface RenameInfo {
canRename: boolean;
localizedErrorMessage?: string;
export type RenameInfo = RenameInfoSuccess | RenameInfoFailure;
export interface RenameInfoSuccess {
canRename: true;
/**
* File or directory to rename.
* If set, `getEditsForFileRename` should be called instead of `findRenameLocations`.
*/
fileToRename?: string;
displayName: string;
fullDisplayName: string;
kind: ScriptElementKind;
kindModifiers: string;
triggerSpan: TextSpan;
}
export interface RenameInfoFailure {
canRename: false;
localizedErrorMessage: string;
}
export interface SignatureHelpParameter {
name: string;
+34 -16
View File
@@ -23,8 +23,10 @@ namespace ts {
export function getMeaningFromDeclaration(node: Node): SemanticMeaning {
switch (node.kind) {
case SyntaxKind.Parameter:
case SyntaxKind.VariableDeclaration:
return isInJSFile(node) && getJSDocEnumTag(node) ? SemanticMeaning.All : SemanticMeaning.Value;
case SyntaxKind.Parameter:
case SyntaxKind.BindingElement:
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
@@ -224,6 +226,14 @@ namespace ts {
return undefined;
}
export function hasPropertyAccessExpressionWithName(node: CallExpression, funcName: string): boolean {
if (!isPropertyAccessExpression(node.expression)) {
return false;
}
return node.expression.name.text === funcName;
}
export function isJumpStatementTarget(node: Node): node is Identifier & { parent: BreakOrContinueStatement } {
return node.kind === SyntaxKind.Identifier && isBreakOrContinueStatement(node.parent) && node.parent.label === node;
}
@@ -355,23 +365,23 @@ namespace ts {
case SyntaxKind.NamespaceImport:
return ScriptElementKind.alias;
case SyntaxKind.BinaryExpression:
const kind = getSpecialPropertyAssignmentKind(node as BinaryExpression);
const kind = getAssignmentDeclarationKind(node as BinaryExpression);
const { right } = node as BinaryExpression;
switch (kind) {
case SpecialPropertyAssignmentKind.None:
case AssignmentDeclarationKind.None:
return ScriptElementKind.unknown;
case SpecialPropertyAssignmentKind.ExportsProperty:
case SpecialPropertyAssignmentKind.ModuleExports:
case AssignmentDeclarationKind.ExportsProperty:
case AssignmentDeclarationKind.ModuleExports:
const rightKind = getNodeKind(right);
return rightKind === ScriptElementKind.unknown ? ScriptElementKind.constElement : rightKind;
case SpecialPropertyAssignmentKind.PrototypeProperty:
case AssignmentDeclarationKind.PrototypeProperty:
return isFunctionExpression(right) ? ScriptElementKind.memberFunctionElement : ScriptElementKind.memberVariableElement;
case SpecialPropertyAssignmentKind.ThisProperty:
case AssignmentDeclarationKind.ThisProperty:
return ScriptElementKind.memberVariableElement; // property
case SpecialPropertyAssignmentKind.Property:
case AssignmentDeclarationKind.Property:
// static method / property
return isFunctionExpression(right) ? ScriptElementKind.memberFunctionElement : ScriptElementKind.memberVariableElement;
case SpecialPropertyAssignmentKind.Prototype:
case AssignmentDeclarationKind.Prototype:
return ScriptElementKind.localClassElement;
default: {
assertType<never>(kind);
@@ -1315,7 +1325,16 @@ namespace ts {
});
}
export function getPropertySymbolFromBindingElement(checker: TypeChecker, bindingElement: BindingElement & { name: Identifier }) {
export type ObjectBindingElementWithoutPropertyName = BindingElement & { name: Identifier };
export function isObjectBindingElementWithoutPropertyName(bindingElement: Node): bindingElement is ObjectBindingElementWithoutPropertyName {
return isBindingElement(bindingElement) &&
isObjectBindingPattern(bindingElement.parent) &&
isIdentifier(bindingElement.name) &&
!bindingElement.propertyName;
}
export function getPropertySymbolFromBindingElement(checker: TypeChecker, bindingElement: ObjectBindingElementWithoutPropertyName) {
const typeOfPattern = checker.getTypeAtLocation(bindingElement.parent);
const propSymbol = typeOfPattern && checker.getPropertyOfType(typeOfPattern, bindingElement.name.text);
if (propSymbol && propSymbol.flags & SymbolFlags.Accessor) {
@@ -1654,11 +1673,10 @@ namespace ts {
return clone;
}
export function getSynthesizedDeepCloneWithRenames<T extends Node | undefined>(node: T, includeTrivia = true, renameMap?: Map<Identifier>, checker?: TypeChecker, callback?: (originalNode: Node, clone: Node) => any): T {
export function getSynthesizedDeepCloneWithRenames<T extends Node>(node: T, includeTrivia = true, renameMap?: Map<Identifier>, checker?: TypeChecker, callback?: (originalNode: Node, clone: Node) => any): T {
let clone;
if (node && isIdentifier(node!) && renameMap && checker) {
const symbol = checker.getSymbolAtLocation(node!);
if (isIdentifier(node) && renameMap && checker) {
const symbol = checker.getSymbolAtLocation(node);
const renameInfo = symbol && renameMap.get(String(getSymbolId(symbol)));
if (renameInfo) {
@@ -1667,11 +1685,11 @@ namespace ts {
}
if (!clone) {
clone = node && getSynthesizedDeepCloneWorker(node as NonNullable<T>, renameMap, checker, callback);
clone = getSynthesizedDeepCloneWorker(node as NonNullable<T>, renameMap, checker, callback);
}
if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone);
if (callback && node) callback(node!, clone);
if (callback && clone) callback(node, clone);
return clone as T;
}
+1 -1
View File
@@ -208,7 +208,7 @@ class CompilerTest {
public verifyModuleResolution() {
if (this.options.traceResolution) {
Harness.Baseline.runBaseline(this.justName.replace(/\.tsx?$/, ".trace.json"),
utils.removeTestPathPrefixes(JSON.stringify(this.result.traces, undefined, 4)));
JSON.stringify(this.result.traces.map(utils.sanitizeTraceResolutionLogEntry), undefined, 4));
}
}
+16 -14
View File
@@ -1,6 +1,6 @@
const fs = require("fs");
const path = require("path");
const del = require("del");
const fs: typeof import("fs") = require("fs");
const path: typeof import("path") = require("path");
const del: typeof import("del") = require("del");
interface ExecResult {
stdout: Buffer;
@@ -40,7 +40,7 @@ abstract class ExternalCompileRunnerBase extends RunnerBase {
const timeout = 600_000; // 10 minutes
describe(directoryName, function(this: Mocha.ISuiteCallbackContext) {
this.timeout(timeout);
const cp = require("child_process");
const cp: typeof import("child_process") = require("child_process");
it("should build successfully", () => {
let cwd = path.join(Harness.IO.getWorkspaceRoot(), cls.testDir, directoryName);
@@ -49,12 +49,9 @@ abstract class ExternalCompileRunnerBase extends RunnerBase {
let types: string[] | undefined;
if (fs.existsSync(path.join(cwd, "test.json"))) {
const submoduleDir = path.join(cwd, directoryName);
const reset = cp.spawnSync("git", ["reset", "HEAD", "--hard"], { cwd: submoduleDir, timeout, shell: true, stdio });
if (reset.status !== 0) throw new Error(`git reset for ${directoryName} failed: ${reset.stderr.toString()}`);
const clean = cp.spawnSync("git", ["clean", "-f"], { cwd: submoduleDir, timeout, shell: true, stdio });
if (clean.status !== 0) throw new Error(`git clean for ${directoryName} failed: ${clean.stderr.toString()}`);
const update = cp.spawnSync("git", ["submodule", "update", "--remote", "."], { cwd: submoduleDir, timeout, shell: true, stdio });
if (update.status !== 0) throw new Error(`git submodule update for ${directoryName} failed: ${update.stderr.toString()}`);
exec("git", ["reset", "HEAD", "--hard"], { cwd: submoduleDir });
exec("git", ["clean", "-f"], { cwd: submoduleDir });
exec("git", ["submodule", "update", "--init", "--remote", "."], { cwd: submoduleDir });
const config = JSON.parse(fs.readFileSync(path.join(cwd, "test.json"), { encoding: "utf8" })) as UserConfig;
ts.Debug.assert(!!config.types, "Bad format from test.json: Types field must be present.");
@@ -69,18 +66,23 @@ abstract class ExternalCompileRunnerBase extends RunnerBase {
if (fs.existsSync(path.join(cwd, "node_modules"))) {
del.sync(path.join(cwd, "node_modules"), { force: true });
}
const install = cp.spawnSync(`npm`, ["i", "--ignore-scripts"], { cwd, timeout: timeout / 2, shell: true, stdio }); // NPM shouldn't take the entire timeout - if it takes a long time, it should be terminated and we should log the failure
if (install.status !== 0) throw new Error(`NPM Install for ${directoryName} failed: ${install.stderr.toString()}`);
exec("npm", ["i", "--ignore-scripts"], { cwd, timeout: timeout / 2 }); // NPM shouldn't take the entire timeout - if it takes a long time, it should be terminated and we should log the failure
}
const args = [path.join(Harness.IO.getWorkspaceRoot(), "built/local/tsc.js")];
if (types) {
args.push("--types", types.join(","));
// Also actually install those types (for, eg, the js projects which need node)
const install = cp.spawnSync(`npm`, ["i", ...types.map(t => `@types/${t}`), "--no-save", "--ignore-scripts"], { cwd: originalCwd, timeout: timeout / 2, shell: true, stdio }); // NPM shouldn't take the entire timeout - if it takes a long time, it should be terminated and we should log the failure
if (install.status !== 0) throw new Error(`NPM Install types for ${directoryName} failed: ${install.stderr.toString()}`);
exec("npm", ["i", ...types.map(t => `@types/${t}`), "--no-save", "--ignore-scripts"], { cwd: originalCwd, timeout: timeout / 2 }); // NPM shouldn't take the entire timeout - if it takes a long time, it should be terminated and we should log the failure
}
args.push("--noEmit");
Harness.Baseline.runBaseline(`${cls.kind()}/${directoryName}.log`, cls.report(cp.spawnSync(`node`, args, { cwd, timeout, shell: true }), cwd));
function exec(command: string, args: string[], options: { cwd: string, timeout?: number }): void {
const res = cp.spawnSync(command, args, { timeout, shell: true, stdio, ...options });
if (res.status !== 0) {
throw new Error(`${command} ${args.join(" ")} for ${directoryName} failed: ${res.stderr && res.stderr.toString()}`);
}
}
});
});
}
-1
View File
@@ -138,7 +138,6 @@ function handleTestConfig() {
case "compiler":
runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance));
runners.push(new CompilerBaselineRunner(CompilerTestType.Regressions));
runners.push(new project.ProjectRunner());
break;
case "conformance":
runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance));
+1
View File
@@ -74,6 +74,7 @@
"unittests/publicApi.ts",
"unittests/reuseProgramStructure.ts",
"unittests/session.ts",
"unittests/semver.ts",
"unittests/symbolWalker.ts",
"unittests/telemetry.ts",
"unittests/textChanges.ts",
+1 -1
View File
@@ -36,7 +36,7 @@ namespace ts.projectSystem {
typingsInstaller: typingsInstaller || server.nullTypingsInstaller,
byteLength: Utils.byteLength,
hrtime: process.hrtime,
logger: nullLogger,
logger: createHasErrorMessageLogger().logger,
canUseEvents: false
};
return new server.Session(opts);
@@ -96,6 +96,11 @@ namespace ts {
include: null,
files: ["../main.ts"]
}),
"dev/configs/fifth.json": JSON.stringify({
extends: "./fourth",
include: ["../tests/utils.ts"],
files: []
}),
"dev/extends.json": JSON.stringify({ extends: 42 }),
"dev/extends2.json": JSON.stringify({ extends: "configs/base" }),
"dev/main.ts": "",
@@ -244,6 +249,29 @@ namespace ts {
}, [
combinePaths(basePath, "main.ts")
]);
testSuccess("can overwrite top-level files using extended []", "configs/fifth.json", {
allowJs: true,
noImplicitAny: true,
strictNullChecks: true,
module: ModuleKind.System
}, [
combinePaths(basePath, "tests/utils.ts")
]);
it("adds extendedSourceFiles only once", () => {
const sourceFile = readJsonConfigFile("configs/fourth.json", (path) => host.readFile(path));
const dir = combinePaths(basePath, "configs");
const expected = [
combinePaths(dir, "third.json"),
combinePaths(dir, "second.json"),
combinePaths(dir, "base.json"),
];
parseJsonSourceFileConfigFileContent(sourceFile, host, dir, {}, "fourth.json");
assert.deepEqual(sourceFile.extendedSourceFiles, expected);
parseJsonSourceFileConfigFileContent(sourceFile, host, dir, {}, "fourth.json");
assert.deepEqual(sourceFile.extendedSourceFiles, expected);
});
});
});
});
@@ -1,33 +1,50 @@
namespace ts {
describe("convertCompilerOptionsFromJson", () => {
function assertCompilerOptions(json: any, configFileName: string, expectedResult: { compilerOptions: CompilerOptions, errors: Diagnostic[] }) {
const formatDiagnosticHost: FormatDiagnosticsHost = {
getCurrentDirectory: () => "/apath/",
getCanonicalFileName: createGetCanonicalFileName(/*useCaseSensitiveFileNames*/ true),
getNewLine: () => "\n"
};
interface ExpectedResultWithParsingSuccess {
compilerOptions: CompilerOptions;
errors: ReadonlyArray<Diagnostic>;
}
interface ExpectedResultWithParsingFailure {
compilerOptions: CompilerOptions;
hasParseErrors: true;
}
type ExpectedResult = ExpectedResultWithParsingSuccess | ExpectedResultWithParsingFailure;
function isExpectedResultWithParsingFailure(expectedResult: ExpectedResult): expectedResult is ExpectedResultWithParsingFailure {
return !!(expectedResult as ExpectedResultWithParsingFailure).hasParseErrors;
}
function assertCompilerOptions(json: any, configFileName: string, expectedResult: ExpectedResultWithParsingSuccess) {
assertCompilerOptionsWithJson(json, configFileName, expectedResult);
assertCompilerOptionsWithJsonNode(json, configFileName, expectedResult);
}
function assertCompilerOptionsWithJson(json: any, configFileName: string, expectedResult: { compilerOptions: CompilerOptions, errors: Diagnostic[] }) {
const { options: actualCompilerOptions, errors: actualErrors} = convertCompilerOptionsFromJson(json.compilerOptions, "/apath/", configFileName);
function assertCompilerOptionsWithJson(json: any, configFileName: string, expectedResult: ExpectedResultWithParsingSuccess) {
const { options: actualCompilerOptions, errors: actualErrors } = convertCompilerOptionsFromJson(json.compilerOptions, "/apath/", configFileName);
const parsedCompilerOptions = JSON.stringify(actualCompilerOptions);
const expectedCompilerOptions = JSON.stringify({ ...expectedResult.compilerOptions, configFilePath: configFileName });
assert.equal(parsedCompilerOptions, expectedCompilerOptions);
const expectedErrors = expectedResult.errors;
assert.isTrue(expectedResult.errors.length === actualErrors.length, `Expected error: ${JSON.stringify(expectedResult.errors)}. Actual error: ${JSON.stringify(actualErrors)}.`);
for (let i = 0; i < actualErrors.length; i++) {
const actualError = actualErrors[i];
const expectedError = expectedErrors[i];
assert.equal(actualError.code, expectedError.code);
assert.equal(actualError.category, expectedError.category);
assert.equal(actualError.messageText, expectedError.messageText);
}
verifyErrors(actualErrors, expectedResult.errors, /*ignoreLocation*/ true);
}
function assertCompilerOptionsWithJsonNode(json: any, configFileName: string, expectedResult: { compilerOptions: CompilerOptions, errors: Diagnostic[] }) {
const fileText = JSON.stringify(json);
function assertCompilerOptionsWithJsonNode(json: any, configFileName: string, expectedResult: ExpectedResultWithParsingSuccess) {
assertCompilerOptionsWithJsonText(JSON.stringify(json), configFileName, expectedResult);
}
function assertCompilerOptionsWithJsonText(fileText: string, configFileName: string, expectedResult: ExpectedResult) {
const result = parseJsonText(configFileName, fileText);
assert(!result.parseDiagnostics.length);
assert(!!result.endOfFileToken);
assert.equal(!!result.parseDiagnostics.length, isExpectedResultWithParsingFailure(expectedResult));
const host: ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: "/apath/" }));
const { options: actualCompilerOptions, errors: actualParseErrors } = parseJsonSourceFileConfigFileContent(result, host, "/apath/", /*existingOptions*/ undefined, configFileName);
expectedResult.compilerOptions.configFilePath = configFileName;
@@ -37,17 +54,32 @@ namespace ts {
assert.equal(parsedCompilerOptions, expectedCompilerOptions);
assert.equal(actualCompilerOptions.configFile, result);
const actualErrors = filter(actualParseErrors, error => error.code !== Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code);
const expectedErrors = expectedResult.errors;
assert.isTrue(expectedResult.errors.length === actualErrors.length, `Expected error: ${JSON.stringify(expectedResult.errors)}. Actual error: ${JSON.stringify(actualErrors)}.`);
if (!isExpectedResultWithParsingFailure(expectedResult)) {
verifyErrors(actualParseErrors.filter(error => error.code !== Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code), expectedResult.errors);
}
}
function verifyErrors(actualErrors: Diagnostic[], expectedErrors: ReadonlyArray<Diagnostic>, ignoreLocation?: boolean) {
assert.isTrue(expectedErrors.length === actualErrors.length, `Expected error: ${JSON.stringify(expectedErrors.map(getDiagnosticString), undefined, " ")}. Actual error: ${JSON.stringify(actualErrors.map(getDiagnosticString), undefined, " ")}.`);
for (let i = 0; i < actualErrors.length; i++) {
const actualError = actualErrors[i];
const expectedError = expectedErrors[i];
assert.equal(actualError.code, expectedError.code, `Expected error-code: ${JSON.stringify(expectedError.code)}. Actual error-code: ${JSON.stringify(actualError.code)}.`);
assert.equal(actualError.category, expectedError.category, `Expected error-category: ${JSON.stringify(expectedError.category)}. Actual error-category: ${JSON.stringify(actualError.category)}.`);
assert(actualError.file);
assert(actualError.start);
assert(actualError.length);
if (!ignoreLocation) {
assert(actualError.file);
assert(actualError.start);
assert(actualError.length);
}
}
function getDiagnosticString(diagnostic: Diagnostic) {
if (ignoreLocation) {
const { file, ...rest } = diagnostic;
diagnostic = { file: undefined, ...rest };
}
return formatDiagnostic(diagnostic, formatDiagnosticHost);
}
}
@@ -539,5 +571,36 @@ namespace ts {
}
);
});
it("Convert tsconfig options when there are multiple invalid strings", () => {
assertCompilerOptionsWithJsonText(`{
"compilerOptions": {
"target": "<%- options.useTsWithBabel ? 'esnext' : 'es5' %>",
"module": "esnext",
<%_ if (options.classComponent) { _%>
"experimentalDecorators": true,
<%_ } _%>
"sourceMap": true,
"types": [
"webpack-env"<% if (hasMocha || hasJest) { %>,<% } %>
<%_ if (hasMocha) { _%>
"mocha",
"chai"
<%_ } else if (hasJest) { _%>
"jest"
<%_ } _%>
]
}
}
`, "tsconfig.json",
{
compilerOptions: {
target: undefined,
module: ModuleKind.ESNext
},
hasParseErrors: true
}
);
});
});
}
+184 -112
View File
@@ -1,68 +1,4 @@
namespace ts {
interface Range {
pos: number;
end: number;
name: string;
}
interface Test {
source: string;
ranges: Map<Range>;
}
function getTest(source: string): Test {
const activeRanges: Range[] = [];
let text = "";
let lastPos = 0;
let pos = 0;
const ranges = createMap<Range>();
while (pos < source.length) {
if (source.charCodeAt(pos) === CharacterCodes.openBracket &&
(source.charCodeAt(pos + 1) === CharacterCodes.hash || source.charCodeAt(pos + 1) === CharacterCodes.$)) {
const saved = pos;
pos += 2;
const s = pos;
consumeIdentifier();
const e = pos;
if (source.charCodeAt(pos) === CharacterCodes.bar) {
pos++;
text += source.substring(lastPos, saved);
const name = s === e
? source.charCodeAt(saved + 1) === CharacterCodes.hash ? "selection" : "extracted"
: source.substring(s, e);
activeRanges.push({ name, pos: text.length, end: undefined! });
lastPos = pos;
continue;
}
else {
pos = saved;
}
}
else if (source.charCodeAt(pos) === CharacterCodes.bar && source.charCodeAt(pos + 1) === CharacterCodes.closeBracket) {
text += source.substring(lastPos, pos);
activeRanges[activeRanges.length - 1].end = text.length;
const range = activeRanges.pop()!;
if (range.name in ranges) {
throw new Error(`Duplicate name of range ${range.name}`);
}
ranges.set(range.name, range);
pos += 2;
lastPos = pos;
continue;
}
pos++;
}
text += source.substring(lastPos, pos);
function consumeIdentifier() {
while (isIdentifierPart(source.charCodeAt(pos), ScriptTarget.Latest)) {
pos++;
}
}
return { source: text, ranges };
}
const libFile: TestFSWithWatch.File = {
path: "/a/lib/lib.d.ts",
content: `/// <reference no-default-lib="true"/>
@@ -319,19 +255,22 @@ interface String { charAt: any; }
interface Array<T> {}`
};
function testConvertToAsyncFunction(caption: string, text: string, baselineFolder: string, diagnosticDescription: DiagnosticMessage, codeFixDescription: DiagnosticMessage, includeLib?: boolean) {
const t = getTest(text);
function testConvertToAsyncFunction(caption: string, text: string, baselineFolder: string, includeLib?: boolean, expectFailure = false) {
const t = extractTest(text);
const selectionRange = t.ranges.get("selection")!;
if (!selectionRange) {
throw new Error(`Test ${caption} does not specify selection range`);
}
[Extension.Ts, Extension.Js].forEach(extension =>
const extensions = expectFailure ? [Extension.Ts] : [Extension.Ts, Extension.Js];
extensions.forEach(extension =>
it(`${caption} [${extension}]`, () => runBaseline(extension)));
function runBaseline(extension: Extension) {
const path = "/a" + extension;
const program = makeProgram({ path, content: t.source }, includeLib)!;
const languageService = makeLanguageService({ path, content: t.source }, includeLib);
const program = languageService.getProgram()!;
if (hasSyntacticDiagnostics(program)) {
// Don't bother generating JS baselines for inputs that aren't valid JS.
@@ -345,10 +284,6 @@ interface Array<T> {}`
};
const sourceFile = program.getSourceFile(path)!;
const host = projectSystem.createServerHost([f, libFile]);
const projectService = projectSystem.createProjectService(host);
projectService.openClientFile(f.path);
const languageService = projectService.inferredProjects[0].getLanguageService();
const context: CodeFixContext = {
errorCode: 80006,
span: { start: selectionRange.pos, length: selectionRange.end - selectionRange.pos },
@@ -357,41 +292,49 @@ interface Array<T> {}`
cancellationToken: { throwIfCancellationRequested: noop, isCancellationRequested: returnFalse },
preferences: emptyOptions,
host: notImplementedHost,
formatContext: formatting.getFormatContext(testFormatOptions)
formatContext: formatting.getFormatContext(testFormatSettings)
};
const diagnostics = languageService.getSuggestionDiagnostics(f.path);
const diagnostic = find(diagnostics, diagnostic => diagnostic.messageText === diagnosticDescription.message);
assert.exists(diagnostic);
assert.equal(diagnostic!.start, context.span.start);
assert.equal(diagnostic!.length, context.span.length);
const diagnostic = find(diagnostics, diagnostic => diagnostic.messageText === Diagnostics.This_may_be_converted_to_an_async_function.message &&
diagnostic.start === context.span.start && diagnostic.length === context.span.length);
if (expectFailure) {
assert.isUndefined(diagnostic);
}
else {
assert.exists(diagnostic);
}
const actions = codefix.getFixes(context);
const action = find(actions, action => action.description === codeFixDescription.message)!;
assert.exists(action);
const action = find(actions, action => action.description === Diagnostics.Convert_to_async_function.message);
if (expectFailure) {
assert.isNotTrue(action && action.changes.length > 0);
return;
}
assert.isTrue(action && action.changes.length > 0);
const data: string[] = [];
data.push(`// ==ORIGINAL==`);
data.push(text.replace("[#|", "/*[#|*/").replace("|]", "/*|]*/"));
const changes = action.changes;
const changes = action!.changes;
assert.lengthOf(changes, 1);
data.push(`// ==ASYNC FUNCTION::${action.description}==`);
data.push(`// ==ASYNC FUNCTION::${action!.description}==`);
const newText = textChanges.applyChanges(sourceFile.text, changes[0].textChanges);
data.push(newText);
const diagProgram = makeProgram({ path, content: newText }, includeLib)!;
const diagProgram = makeLanguageService({ path, content: newText }, includeLib).getProgram()!;
assert.isFalse(hasSyntacticDiagnostics(diagProgram));
Harness.Baseline.runBaseline(`${baselineFolder}/${caption}${extension}`, data.join(newLineCharacter));
}
function makeProgram(f: { path: string, content: string }, includeLib?: boolean) {
function makeLanguageService(f: { path: string, content: string }, includeLib?: boolean) {
const host = projectSystem.createServerHost(includeLib ? [f, libFile] : [f]); // libFile is expensive to parse repeatedly - only test when required
const projectService = projectSystem.createProjectService(host);
projectService.openClientFile(f.path);
const program = projectService.inferredProjects[0].getLanguageService().getProgram();
return program;
return projectService.inferredProjects[0].getLanguageService();
}
function hasSyntacticDiagnostics(program: Program) {
@@ -400,27 +343,6 @@ interface Array<T> {}`
}
}
function testConvertToAsyncFunctionFailed(caption: string, text: string, description: DiagnosticMessage) {
it(caption, () => {
const t = extractTest(text);
const selectionRange = t.ranges.get("selection");
if (!selectionRange) {
throw new Error(`Test ${caption} does not specify selection range`);
}
const f = {
path: "/a.ts",
content: t.source
};
const host = projectSystem.createServerHost([f, libFile]);
const projectService = projectSystem.createProjectService(host);
projectService.openClientFile(f.path);
const languageService = projectService.inferredProjects[0].getLanguageService();
const actions = languageService.getSuggestionDiagnostics(f.path);
assert.isUndefined(find(actions, action => action.messageText === description.message));
});
}
describe("convertToAsyncFunctions", () => {
_testConvertToAsyncFunction("convertToAsyncFunction_basic", `
function [#|f|](): Promise<void>{
@@ -494,6 +416,20 @@ function [#|f|](): Promise<void> {
return fetch('https://typescriptlang.org').then( () => console.log("done") );
}`
);
_testConvertToAsyncFunction("convertToAsyncFunction_IgnoreArgs3", `
function [#|f|](): Promise<void> {
return fetch('https://typescriptlang.org').then( () => console.log("almost done") ).then( () => console.log("done") );
}`
);
_testConvertToAsyncFunction("convertToAsyncFunction_IgnoreArgs4", `
function [#|f|]() {
return fetch('https://typescriptlang.org').then(res);
}
function res(){
console.log("done");
}`
);
_testConvertToAsyncFunction("convertToAsyncFunction_Method", `
class Parser {
[#|f|]():Promise<void> {
@@ -545,6 +481,18 @@ function [#|f|]():Promise<void | Response> {
function [#|f|]():Promise<void | Response> {
return fetch('https://typescriptlang.org').catch(rej => console.log(rej));
}
`
);
_testConvertToAsyncFunction("convertToAsyncFunction_NoRes4", `
function [#|f|]() {
return fetch('https://typescriptlang.org').then(undefined, rejection => console.log("rejected:", rejection));
}
`
);
_testConvertToAsyncFunction("convertToAsyncFunction_NoCatchHandler", `
function [#|f|]() {
return fetch('https://typescriptlang.org').then(x => x.statusText).catch(undefined);
}
`
);
_testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestion", `
@@ -823,7 +771,7 @@ function [#|f|](): Promise<void> {
}
return x.then(resp => {
var blob = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error');
return fetch("https://micorosft.com").then(res => console.log("Another one!"));
return fetch("https://microsoft.com").then(res => console.log("Another one!"));
});
}
`
@@ -889,7 +837,7 @@ function [#|f|]() {
);
_testConvertToAsyncFunction("convertToAsyncFunction_Scope1", `
function [#|f|]() {
var var1:Promise<Response>, var2;
var var1: Response, var2;
return fetch('https://typescriptlang.org').then( _ =>
Promise.resolve().then( res => {
var2 = "test";
@@ -1157,7 +1105,7 @@ function [#|f|]() {
`
);
_testConvertToAsyncFunctionFailed("convertToAsyncFunction_NestedFunction", `
_testConvertToAsyncFunctionFailed("convertToAsyncFunction_NestedFunctionWrongLocation", `
function [#|f|]() {
function fn2(){
function fn3(){
@@ -1167,6 +1115,18 @@ function [#|f|]() {
}
return fn2();
}
`);
_testConvertToAsyncFunction("convertToAsyncFunction_NestedFunctionRightLocation", `
function f() {
function fn2(){
function [#|fn3|](){
return fetch("https://typescriptlang.org").then(res => console.log(res));
}
return fn3();
}
return fn2();
}
`);
_testConvertToAsyncFunction("convertToAsyncFunction_UntypedFunction", `
@@ -1194,14 +1154,126 @@ const [#|foo|] = function () {
}
`);
_testConvertToAsyncFunction("convertToAsyncFunction_simpleFunctionExpressionWithName", `
const foo = function [#|f|]() {
return fetch('https://typescriptlang.org').then(result => { console.log(result) });
}
`);
_testConvertToAsyncFunction("convertToAsyncFunction_simpleFunctionExpressionAssignedToBindingPattern", `
const { length } = [#|function|] () {
return fetch('https://typescriptlang.org').then(result => { console.log(result) });
}
`);
_testConvertToAsyncFunction("convertToAsyncFunction_catchBlockUniqueParams", `
function [#|f|]() {
return Promise.resolve().then(x => 1).catch(x => "a").then(x => !!x);
}
`);
_testConvertToAsyncFunction("convertToAsyncFunction_bindingPattern", `
function [#|f|]() {
return fetch('https://typescriptlang.org').then(res);
}
function res({ status, trailer }){
console.log(status);
}
`);
_testConvertToAsyncFunction("convertToAsyncFunction_bindingPatternNameCollision", `
function [#|f|]() {
const result = 'https://typescriptlang.org';
return fetch(result).then(res);
}
function res({ status, trailer }){
console.log(status);
}
`);
_testConvertToAsyncFunctionFailed("convertToAsyncFunction_thenArgumentNotFunction", `
function [#|f|]() {
return Promise.resolve().then(f ? (x => x) : (y => y));
}
`);
_testConvertToAsyncFunctionFailed("convertToAsyncFunction_thenArgumentNotFunctionNotLastInChain", `
function [#|f|]() {
return Promise.resolve().then(f ? (x => x) : (y => y)).then(q => q);
}
`);
_testConvertToAsyncFunction("convertToAsyncFunction_runEffectfulContinuation", `
function [#|f|]() {
return fetch('https://typescriptlang.org').then(res).then(_ => console.log("done"));
}
function res(result) {
return Promise.resolve().then(x => console.log(result));
}
`);
_testConvertToAsyncFunction("convertToAsyncFunction_callbackReturnsPromise", `
function [#|f|]() {
return fetch('https://typescriptlang.org').then(s => Promise.resolve(s.statusText.length)).then(x => console.log(x + 5));
}
`);
_testConvertToAsyncFunction("convertToAsyncFunction_callbackReturnsPromiseInBlock", `
function [#|f|]() {
return fetch('https://typescriptlang.org').then(s => { return Promise.resolve(s.statusText.length) }).then(x => x + 5);
}
`);
_testConvertToAsyncFunction("convertToAsyncFunction_callbackReturnsFixablePromise", `
function [#|f|]() {
return fetch('https://typescriptlang.org').then(s => Promise.resolve(s.statusText).then(st => st.length)).then(x => console.log(x + 5));
}
`);
_testConvertToAsyncFunction("convertToAsyncFunction_callbackReturnsPromiseLastInChain", `
function [#|f|]() {
return fetch('https://typescriptlang.org').then(s => Promise.resolve(s.statusText.length));
}
`);
_testConvertToAsyncFunction("convertToAsyncFunction_callbackReturnsRejectedPromiseInTryBlock", `
function [#|f|]() {
return Promise.resolve(1)
.then(x => Promise.reject(x))
.catch(err => console.log(err));
}
`);
_testConvertToAsyncFunction("convertToAsyncFunction_nestedPromises", `
function [#|f|]() {
return fetch('https://typescriptlang.org').then(x => Promise.resolve(3).then(y => Promise.resolve(x.statusText.length + y)));
}
`);
_testConvertToAsyncFunction("convertToAsyncFunction_noArgs", `
function delay(millis: number): Promise<void> {
throw "no"
}
function [#|main2|]() {
console.log("Please wait. Loading.");
return delay(500)
.then(() => { console.log("."); return delay(500); })
.then(() => { console.log("."); return delay(500); })
.then(() => { console.log("."); return delay(500); })
}
`);
_testConvertToAsyncFunction("convertToAsyncFunction_exportModifier", `
export function [#|foo|]() {
return fetch('https://typescriptlang.org').then(s => console.log(s));
}
`);
});
function _testConvertToAsyncFunction(caption: string, text: string) {
testConvertToAsyncFunction(caption, text, "convertToAsyncFunction", Diagnostics.This_may_be_converted_to_an_async_function, Diagnostics.Convert_to_async_function, /*includeLib*/ true);
testConvertToAsyncFunction(caption, text, "convertToAsyncFunction", /*includeLib*/ true);
}
function _testConvertToAsyncFunctionFailed(caption: string, text: string) {
testConvertToAsyncFunctionFailed(caption, text, Diagnostics.Convert_to_async_function);
testConvertToAsyncFunction(caption, text, "convertToAsyncFunction", /*includeLib*/ true, /*expectFailure*/ true);
}
}
}
+2 -23
View File
@@ -64,27 +64,6 @@ namespace ts {
}
export const newLineCharacter = "\n";
export const testFormatOptions: FormatCodeSettings = {
indentSize: 4,
tabSize: 4,
newLineCharacter,
convertTabsToSpaces: true,
indentStyle: IndentStyle.Smart,
insertSpaceAfterConstructor: false,
insertSpaceAfterCommaDelimiter: true,
insertSpaceAfterSemicolonInForStatements: true,
insertSpaceBeforeAndAfterBinaryOperators: true,
insertSpaceAfterKeywordsInControlFlowStatements: true,
insertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
insertSpaceBeforeFunctionParenthesis: false,
placeOpenBraceOnNewLineForFunctions: false,
placeOpenBraceOnNewLineForControlBlocks: false,
};
export const notImplementedHost: LanguageServiceHost = {
getCompilationSettings: notImplemented,
@@ -123,7 +102,7 @@ namespace ts {
startPosition: selectionRange.pos,
endPosition: selectionRange.end,
host: notImplementedHost,
formatContext: formatting.getFormatContext(testFormatOptions),
formatContext: formatting.getFormatContext(testFormatSettings),
preferences: emptyOptions,
};
const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromRange(selectionRange));
@@ -185,7 +164,7 @@ namespace ts {
startPosition: selectionRange.pos,
endPosition: selectionRange.end,
host: notImplementedHost,
formatContext: formatting.getFormatContext(testFormatOptions),
formatContext: formatting.getFormatContext(testFormatSettings),
preferences: emptyOptions,
};
const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromRange(selectionRange));
+25 -11
View File
@@ -83,7 +83,7 @@ namespace ts {
describe("Node module resolution - relative paths", () => {
function testLoadAsFile(containingFileName: string, moduleFileNameNoExt: string, moduleName: string): void {
for (const ext of supportedTypeScriptExtensions) {
for (const ext of supportedTSExtensions) {
test(ext, /*hasDirectoryExists*/ false);
test(ext, /*hasDirectoryExists*/ true);
}
@@ -96,7 +96,7 @@ namespace ts {
const failedLookupLocations: string[] = [];
const dir = getDirectoryPath(containingFileName);
for (const e of supportedTypeScriptExtensions) {
for (const e of supportedTSExtensions) {
if (e === ext) {
break;
}
@@ -137,7 +137,7 @@ namespace ts {
const resolution = nodeModuleNameResolver(moduleName, containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, packageJson, moduleFile));
checkResolvedModule(resolution.resolvedModule, createResolvedModule(moduleFile.name));
// expect three failed lookup location - attempt to load module as file with all supported extensions
assert.equal(resolution.failedLookupLocations.length, supportedTypeScriptExtensions.length);
assert.equal(resolution.failedLookupLocations.length, supportedTSExtensions.length);
}
}
@@ -1104,14 +1104,18 @@ import b = require("./moduleB");
});
describe("Type reference directive resolution: ", () => {
function test(typesRoot: string, typeDirective: string, primary: boolean, initialFile: File, targetFile: File, ...otherFiles: File[]) {
const host = createModuleResolutionHost(/*hasDirectoryExists*/ false, ...[initialFile, targetFile].concat(...otherFiles));
const result = resolveTypeReferenceDirective(typeDirective, initialFile.name, { typeRoots: [typesRoot] }, host);
function testWorker(hasDirectoryExists: boolean, typesRoot: string | undefined, typeDirective: string, primary: boolean, initialFile: File, targetFile: File, ...otherFiles: File[]) {
const host = createModuleResolutionHost(hasDirectoryExists, ...[initialFile, targetFile].concat(...otherFiles));
const result = resolveTypeReferenceDirective(typeDirective, initialFile.name, typesRoot ? { typeRoots: [typesRoot] } : {}, host);
assert(result.resolvedTypeReferenceDirective!.resolvedFileName !== undefined, "expected type directive to be resolved");
assert.equal(result.resolvedTypeReferenceDirective!.resolvedFileName, targetFile.name, "unexpected result of type reference resolution");
assert.equal(result.resolvedTypeReferenceDirective!.primary, primary, "unexpected 'primary' value");
}
function test(typesRoot: string, typeDirective: string, primary: boolean, initialFile: File, targetFile: File, ...otherFiles: File[]) {
testWorker(/*hasDirectoryExists*/ false, typesRoot, typeDirective, primary, initialFile, targetFile, ...otherFiles);
}
it("Can be resolved from primary location", () => {
{
const f1 = { name: "/root/src/app.ts" };
@@ -1194,7 +1198,7 @@ import b = require("./moduleB");
const names = map(files, f => f.name);
const sourceFiles = arrayToMap(map(files, f => createSourceFile(f.name, f.content, ScriptTarget.ES2015)), f => f.fileName);
const compilerHost: CompilerHost = {
fileExists : fileName => sourceFiles.has(fileName),
fileExists: fileName => sourceFiles.has(fileName),
getSourceFile: fileName => sourceFiles.get(fileName),
getDefaultLibFileName: () => "lib.d.ts",
writeFile: notImplemented,
@@ -1219,7 +1223,7 @@ import b = require("./moduleB");
assert.equal(diagnostics1[0].messageText, diagnostics2[0].messageText, "expected one diagnostic");
});
it ("Modules in the same .d.ts file are preferred to external files", () => {
it("Modules in the same .d.ts file are preferred to external files", () => {
const f = {
name: "/a/b/c/c/app.d.ts",
content: `
@@ -1233,7 +1237,7 @@ import b = require("./moduleB");
};
const file = createSourceFile(f.name, f.content, ScriptTarget.ES2015);
const compilerHost: CompilerHost = {
fileExists : fileName => fileName === file.fileName,
fileExists: fileName => fileName === file.fileName,
getSourceFile: fileName => fileName === file.fileName ? file : undefined,
getDefaultLibFileName: () => "lib.d.ts",
writeFile: notImplemented,
@@ -1248,7 +1252,7 @@ import b = require("./moduleB");
createProgram([f.name], {}, compilerHost);
});
it ("Modules in .ts file are not checked in the same file", () => {
it("Modules in .ts file are not checked in the same file", () => {
const f = {
name: "/a/b/c/c/app.ts",
content: `
@@ -1262,7 +1266,7 @@ import b = require("./moduleB");
};
const file = createSourceFile(f.name, f.content, ScriptTarget.ES2015);
const compilerHost: CompilerHost = {
fileExists : fileName => fileName === file.fileName,
fileExists: fileName => fileName === file.fileName,
getSourceFile: fileName => fileName === file.fileName ? file : undefined,
getDefaultLibFileName: () => "lib.d.ts",
writeFile: notImplemented,
@@ -1279,5 +1283,15 @@ import b = require("./moduleB");
};
createProgram([f.name], {}, compilerHost);
});
describe("can be resolved when typeReferenceDirective is relative and in a sibling folder", () => {
const initialFile = { name: "/root/src/background/app.ts" };
const targetFile = { name: "/root/src/typedefs/filesystem.d.ts" };
it("when host doesnt have directoryExists", () => {
testWorker(/*hasDirectoryExists*/ false, /*typesRoot*/ undefined, /*typeDirective*/ "../typedefs/filesystem", /*primary*/ false, initialFile, targetFile);
});
it("when host has directoryExists", () => {
testWorker(/*hasDirectoryExists*/ true, /*typesRoot*/ undefined, /*typeDirective*/ "../typedefs/filesystem", /*primary*/ false, initialFile, targetFile);
});
});
});
}
+2 -2
View File
@@ -270,7 +270,7 @@ export const Other = 1;
content: "function F() { }",
};
const languageService = makeLanguageService(testFile);
const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatOptions, emptyOptions);
const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions);
assert.isEmpty(changes);
});
@@ -741,7 +741,7 @@ export * from "lib";
function runBaseline(baselinePath: string, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) {
const { path: testPath, content: testContent } = testFile;
const languageService = makeLanguageService(testFile, ...otherFiles);
const changes = languageService.organizeImports({ type: "file", fileName: testPath }, testFormatOptions, emptyOptions);
const changes = languageService.organizeImports({ type: "file", fileName: testPath }, testFormatSettings, emptyOptions);
assert.equal(changes.length, 1);
assert.equal(changes[0].fileName, testPath);
@@ -98,4 +98,27 @@ namespace ts {
]);
});
});
describe("Program.isSourceFileFromExternalLibrary", () => {
it("works on redirect files", () => {
// In this example '/node_modules/foo/index.d.ts' will redirect to '/node_modules/bar/node_modules/foo/index.d.ts'.
const a = new documents.TextDocument("/a.ts", 'import * as bar from "bar"; import * as foo from "foo";');
const bar = new documents.TextDocument("/node_modules/bar/index.d.ts", 'import * as foo from "foo";');
const fooPackageJsonText = '{ "name": "foo", "version": "1.2.3" }';
const fooIndexText = "export const x: number;";
const barFooPackage = new documents.TextDocument("/node_modules/bar/node_modules/foo/package.json", fooPackageJsonText);
const barFooIndex = new documents.TextDocument("/node_modules/bar/node_modules/foo/index.d.ts", fooIndexText);
const fooPackage = new documents.TextDocument("/node_modules/foo/package.json", fooPackageJsonText);
const fooIndex = new documents.TextDocument("/node_modules/foo/index.d.ts", fooIndexText);
const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [a, bar, barFooPackage, barFooIndex, fooPackage, fooIndex], cwd: "/" });
const program = createProgram(["/a.ts"], emptyOptions, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed }));
for (const file of [a, bar, barFooIndex, fooIndex]) {
const isExternalExpected = file !== a;
const isExternalActual = program.isSourceFileFromExternalLibrary(program.getSourceFile(file.file)!);
assert.equal(isExternalActual, isExternalExpected, `Expected ${file.file} isSourceFileFromExternalLibrary to be ${isExternalExpected}, got ${isExternalActual}`);
}
});
});
}
@@ -104,7 +104,7 @@ namespace ts {
return file;
}
export function createTestCompilerHost(texts: ReadonlyArray<NamedSourceText>, target: ScriptTarget, oldProgram?: ProgramWithSourceTexts): TestCompilerHost {
export function createTestCompilerHost(texts: ReadonlyArray<NamedSourceText>, target: ScriptTarget, oldProgram?: ProgramWithSourceTexts, useGetSourceFileByPath?: boolean) {
const files = arrayToMap(texts, t => t.name, t => {
if (oldProgram) {
let oldFile = <SourceFileWithText>oldProgram.getSourceFile(t.name);
@@ -117,55 +117,47 @@ namespace ts {
}
return createSourceFileWithText(t.name, t.text, target);
});
const useCaseSensitiveFileNames = sys && sys.useCaseSensitiveFileNames;
const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
const trace: string[] = [];
return {
const result: TestCompilerHost = {
trace: s => trace.push(s),
getTrace: () => trace,
getSourceFile(fileName): SourceFile {
return files.get(fileName)!;
},
getDefaultLibFileName(): string {
return "lib.d.ts";
},
getSourceFile: fileName => files.get(fileName),
getDefaultLibFileName: () => "lib.d.ts",
writeFile: notImplemented,
getCurrentDirectory(): string {
return "";
},
getDirectories(): string[] {
return [];
},
getCanonicalFileName(fileName): string {
return sys && sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase();
},
useCaseSensitiveFileNames(): boolean {
return sys && sys.useCaseSensitiveFileNames;
},
getNewLine(): string {
return sys ? sys.newLine : newLine;
},
getCurrentDirectory: () => "",
getDirectories: () => [],
getCanonicalFileName,
useCaseSensitiveFileNames: () => useCaseSensitiveFileNames,
getNewLine: () => sys ? sys.newLine : newLine,
fileExists: fileName => files.has(fileName),
readFile: fileName => {
const file = files.get(fileName);
return file && file.text;
},
};
if (useGetSourceFileByPath) {
const filesByPath = mapEntries(files, (fileName, file) => [toPath(fileName, "", getCanonicalFileName), file]);
result.getSourceFileByPath = (_fileName, path) => filesByPath.get(path);
}
return result;
}
export function newProgram(texts: NamedSourceText[], rootNames: string[], options: CompilerOptions): ProgramWithSourceTexts {
const host = createTestCompilerHost(texts, options.target!);
export function newProgram(texts: NamedSourceText[], rootNames: string[], options: CompilerOptions, useGetSourceFileByPath?: boolean): ProgramWithSourceTexts {
const host = createTestCompilerHost(texts, options.target!, /*oldProgram*/ undefined, useGetSourceFileByPath);
const program = <ProgramWithSourceTexts>createProgram(rootNames, options, host);
program.sourceTexts = texts;
program.host = host;
return program;
}
export function updateProgram(oldProgram: ProgramWithSourceTexts, rootNames: ReadonlyArray<string>, options: CompilerOptions, updater: (files: NamedSourceText[]) => void, newTexts?: NamedSourceText[]) {
export function updateProgram(oldProgram: ProgramWithSourceTexts, rootNames: ReadonlyArray<string>, options: CompilerOptions, updater: (files: NamedSourceText[]) => void, newTexts?: NamedSourceText[], useGetSourceFileByPath?: boolean) {
if (!newTexts) {
newTexts = oldProgram.sourceTexts!.slice(0);
}
updater(newTexts);
const host = createTestCompilerHost(newTexts, options.target!, oldProgram);
const host = createTestCompilerHost(newTexts, options.target!, oldProgram, useGetSourceFileByPath);
const program = <ProgramWithSourceTexts>createProgram(rootNames, options, host, oldProgram);
program.sourceTexts = newTexts;
program.host = host;
@@ -305,10 +297,10 @@ namespace ts {
assert.equal(program1.structureIsReused, StructureIsReused.Not);
});
it("fails if rootdir changes", () => {
it("succeeds if rootdir changes", () => {
const program1 = newProgram(files, ["a.ts"], { target, module: ModuleKind.CommonJS, rootDir: "/a/b" });
updateProgram(program1, ["a.ts"], { target, module: ModuleKind.CommonJS, rootDir: "/a/c" }, noop);
assert.equal(program1.structureIsReused, StructureIsReused.Not);
assert.equal(program1.structureIsReused, StructureIsReused.Completely);
});
it("fails if config path changes", () => {
@@ -809,7 +801,7 @@ namespace ts {
const root = "/a.ts";
const compilerOptions = { target, moduleResolution: ModuleResolutionKind.NodeJs };
function createRedirectProgram(options?: { bText: string, bVersion: string }): ProgramWithSourceTexts {
function createRedirectProgram(useGetSourceFileByPath: boolean, options?: { bText: string, bVersion: string }): ProgramWithSourceTexts {
const files: NamedSourceText[] = [
{
name: "/node_modules/a/index.d.ts",
@@ -841,55 +833,64 @@ namespace ts {
},
];
return newProgram(files, [root], compilerOptions);
return newProgram(files, [root], compilerOptions, useGetSourceFileByPath);
}
function updateRedirectProgram(program: ProgramWithSourceTexts, updater: (files: NamedSourceText[]) => void): ProgramWithSourceTexts {
return updateProgram(program, [root], compilerOptions, updater);
function updateRedirectProgram(program: ProgramWithSourceTexts, updater: (files: NamedSourceText[]) => void, useGetSourceFileByPath: boolean): ProgramWithSourceTexts {
return updateProgram(program, [root], compilerOptions, updater, /*newTexts*/ undefined, useGetSourceFileByPath);
}
it("No changes -> redirect not broken", () => {
const program1 = createRedirectProgram();
function verifyRedirects(useGetSourceFileByPath: boolean) {
it("No changes -> redirect not broken", () => {
const program1 = createRedirectProgram(useGetSourceFileByPath);
const program2 = updateRedirectProgram(program1, files => {
updateProgramText(files, root, "const x = 1;");
const program2 = updateRedirectProgram(program1, files => {
updateProgramText(files, root, "const x = 1;");
}, useGetSourceFileByPath);
assert.equal(program1.structureIsReused, StructureIsReused.Completely);
assert.lengthOf(program2.getSemanticDiagnostics(), 0);
});
assert.equal(program1.structureIsReused, StructureIsReused.Completely);
assert.lengthOf(program2.getSemanticDiagnostics(), 0);
it("Target changes -> redirect broken", () => {
const program1 = createRedirectProgram(useGetSourceFileByPath);
assert.lengthOf(program1.getSemanticDiagnostics(), 0);
const program2 = updateRedirectProgram(program1, files => {
updateProgramText(files, axIndex, "export default class X { private x: number; private y: number; }");
updateProgramText(files, axPackage, JSON.stringify('{ name: "x", version: "1.2.4" }'));
}, useGetSourceFileByPath);
assert.equal(program1.structureIsReused, StructureIsReused.Not);
assert.lengthOf(program2.getSemanticDiagnostics(), 1);
});
it("Underlying changes -> redirect broken", () => {
const program1 = createRedirectProgram(useGetSourceFileByPath);
const program2 = updateRedirectProgram(program1, files => {
updateProgramText(files, bxIndex, "export default class X { private x: number; private y: number; }");
updateProgramText(files, bxPackage, JSON.stringify({ name: "x", version: "1.2.4" }));
}, useGetSourceFileByPath);
assert.equal(program1.structureIsReused, StructureIsReused.Not);
assert.lengthOf(program2.getSemanticDiagnostics(), 1);
});
it("Previously duplicate packages -> program structure not reused", () => {
const program1 = createRedirectProgram(useGetSourceFileByPath, { bVersion: "1.2.4", bText: "export = class X { private x: number; }" });
const program2 = updateRedirectProgram(program1, files => {
updateProgramText(files, bxIndex, "export default class X { private x: number; }");
updateProgramText(files, bxPackage, JSON.stringify({ name: "x", version: "1.2.3" }));
}, useGetSourceFileByPath);
assert.equal(program1.structureIsReused, StructureIsReused.Not);
assert.deepEqual(program2.getSemanticDiagnostics(), []);
});
}
describe("when host implements getSourceFile", () => {
verifyRedirects(/*useGetSourceFileByPath*/ false);
});
it("Target changes -> redirect broken", () => {
const program1 = createRedirectProgram();
assert.lengthOf(program1.getSemanticDiagnostics(), 0);
const program2 = updateRedirectProgram(program1, files => {
updateProgramText(files, axIndex, "export default class X { private x: number; private y: number; }");
updateProgramText(files, axPackage, JSON.stringify('{ name: "x", version: "1.2.4" }'));
});
assert.equal(program1.structureIsReused, StructureIsReused.Not);
assert.lengthOf(program2.getSemanticDiagnostics(), 1);
});
it("Underlying changes -> redirect broken", () => {
const program1 = createRedirectProgram();
const program2 = updateRedirectProgram(program1, files => {
updateProgramText(files, bxIndex, "export default class X { private x: number; private y: number; }");
updateProgramText(files, bxPackage, JSON.stringify({ name: "x", version: "1.2.4" }));
});
assert.equal(program1.structureIsReused, StructureIsReused.Not);
assert.lengthOf(program2.getSemanticDiagnostics(), 1);
});
it("Previously duplicate packages -> program structure not reused", () => {
const program1 = createRedirectProgram({ bVersion: "1.2.4", bText: "export = class X { private x: number; }" });
const program2 = updateRedirectProgram(program1, files => {
updateProgramText(files, bxIndex, "export default class X { private x: number; }");
updateProgramText(files, bxPackage, JSON.stringify({ name: "x", version: "1.2.3" }));
});
assert.equal(program1.structureIsReused, StructureIsReused.Not);
assert.deepEqual(program2.getSemanticDiagnostics(), []);
describe("when host implements getSourceFileByPath", () => {
verifyRedirects(/*useGetSourceFileByPath*/ true);
});
});
});
@@ -914,7 +915,8 @@ namespace ts {
program, newRootFileNames, newOptions,
path => program.getSourceFileByPath(path)!.version, /*fileExists*/ returnFalse,
/*hasInvalidatedResolution*/ returnFalse,
/*hasChangedAutomaticTypeDirectiveNames*/ false
/*hasChangedAutomaticTypeDirectiveNames*/ false,
/*projectReferences*/ undefined
);
assert.isTrue(actual);
}
+228
View File
@@ -0,0 +1,228 @@
namespace ts {
import theory = utils.theory;
describe("semver", () => {
describe("Version", () => {
function assertVersion(version: Version, [major, minor, patch, prerelease, build]: [number, number, number, string[]?, string[]?]) {
assert.strictEqual(version.major, major);
assert.strictEqual(version.minor, minor);
assert.strictEqual(version.patch, patch);
assert.deepEqual(version.prerelease, prerelease || emptyArray);
assert.deepEqual(version.build, build || emptyArray);
}
describe("new", () => {
it("text", () => {
assertVersion(new Version("1.2.3-pre.4+build.5"), [1, 2, 3, ["pre", "4"], ["build", "5"]]);
});
it("parts", () => {
assertVersion(new Version(1, 2, 3, "pre.4", "build.5"), [1, 2, 3, ["pre", "4"], ["build", "5"]]);
assertVersion(new Version(1, 2, 3), [1, 2, 3]);
assertVersion(new Version(1, 2), [1, 2, 0]);
assertVersion(new Version(1), [1, 0, 0]);
});
});
it("toString", () => {
assert.strictEqual(new Version(1, 2, 3, "pre.4", "build.5").toString(), "1.2.3-pre.4+build.5");
assert.strictEqual(new Version(1, 2, 3, "pre.4").toString(), "1.2.3-pre.4");
assert.strictEqual(new Version(1, 2, 3, /*prerelease*/ undefined, "build.5").toString(), "1.2.3+build.5");
assert.strictEqual(new Version(1, 2, 3).toString(), "1.2.3");
assert.strictEqual(new Version(1, 2).toString(), "1.2.0");
assert.strictEqual(new Version(1).toString(), "1.0.0");
});
it("compareTo", () => {
// https://semver.org/#spec-item-11
// > Precedence is determined by the first difference when comparing each of these
// > identifiers from left to right as follows: Major, minor, and patch versions are
// > always compared numerically.
assert.strictEqual(new Version("1.0.0").compareTo(new Version("2.0.0")), Comparison.LessThan);
assert.strictEqual(new Version("1.0.0").compareTo(new Version("1.1.0")), Comparison.LessThan);
assert.strictEqual(new Version("1.0.0").compareTo(new Version("1.0.1")), Comparison.LessThan);
assert.strictEqual(new Version("2.0.0").compareTo(new Version("1.0.0")), Comparison.GreaterThan);
assert.strictEqual(new Version("1.1.0").compareTo(new Version("1.0.0")), Comparison.GreaterThan);
assert.strictEqual(new Version("1.0.1").compareTo(new Version("1.0.0")), Comparison.GreaterThan);
assert.strictEqual(new Version("1.0.0").compareTo(new Version("1.0.0")), Comparison.EqualTo);
// https://semver.org/#spec-item-11
// > When major, minor, and patch are equal, a pre-release version has lower
// > precedence than a normal version.
assert.strictEqual(new Version("1.0.0").compareTo(new Version("1.0.0-pre")), Comparison.GreaterThan);
assert.strictEqual(new Version("1.0.1-pre").compareTo(new Version("1.0.0")), Comparison.GreaterThan);
assert.strictEqual(new Version("1.0.0-pre").compareTo(new Version("1.0.0")), Comparison.LessThan);
// https://semver.org/#spec-item-11
// > identifiers consisting of only digits are compared numerically
assert.strictEqual(new Version("1.0.0-0").compareTo(new Version("1.0.0-1")), Comparison.LessThan);
assert.strictEqual(new Version("1.0.0-1").compareTo(new Version("1.0.0-0")), Comparison.GreaterThan);
assert.strictEqual(new Version("1.0.0-2").compareTo(new Version("1.0.0-10")), Comparison.LessThan);
assert.strictEqual(new Version("1.0.0-10").compareTo(new Version("1.0.0-2")), Comparison.GreaterThan);
assert.strictEqual(new Version("1.0.0-0").compareTo(new Version("1.0.0-0")), Comparison.EqualTo);
// https://semver.org/#spec-item-11
// > identifiers with letters or hyphens are compared lexically in ASCII sort order.
assert.strictEqual(new Version("1.0.0-a").compareTo(new Version("1.0.0-b")), Comparison.LessThan);
assert.strictEqual(new Version("1.0.0-a-2").compareTo(new Version("1.0.0-a-10")), Comparison.GreaterThan);
assert.strictEqual(new Version("1.0.0-b").compareTo(new Version("1.0.0-a")), Comparison.GreaterThan);
assert.strictEqual(new Version("1.0.0-a").compareTo(new Version("1.0.0-a")), Comparison.EqualTo);
assert.strictEqual(new Version("1.0.0-A").compareTo(new Version("1.0.0-a")), Comparison.LessThan);
// https://semver.org/#spec-item-11
// > Numeric identifiers always have lower precedence than non-numeric identifiers.
assert.strictEqual(new Version("1.0.0-0").compareTo(new Version("1.0.0-alpha")), Comparison.LessThan);
assert.strictEqual(new Version("1.0.0-alpha").compareTo(new Version("1.0.0-0")), Comparison.GreaterThan);
assert.strictEqual(new Version("1.0.0-0").compareTo(new Version("1.0.0-0")), Comparison.EqualTo);
assert.strictEqual(new Version("1.0.0-alpha").compareTo(new Version("1.0.0-alpha")), Comparison.EqualTo);
// https://semver.org/#spec-item-11
// > A larger set of pre-release fields has a higher precedence than a smaller set, if all
// > of the preceding identifiers are equal.
assert.strictEqual(new Version("1.0.0-alpha").compareTo(new Version("1.0.0-alpha.0")), Comparison.LessThan);
assert.strictEqual(new Version("1.0.0-alpha.0").compareTo(new Version("1.0.0-alpha")), Comparison.GreaterThan);
// https://semver.org/#spec-item-11
// > Precedence for two pre-release versions with the same major, minor, and patch version
// > MUST be determined by comparing each dot separated identifier from left to right until
// > a difference is found [...]
assert.strictEqual(new Version("1.0.0-a.0.b.1").compareTo(new Version("1.0.0-a.0.b.2")), Comparison.LessThan);
assert.strictEqual(new Version("1.0.0-a.0.b.1").compareTo(new Version("1.0.0-b.0.a.1")), Comparison.LessThan);
assert.strictEqual(new Version("1.0.0-a.0.b.2").compareTo(new Version("1.0.0-a.0.b.1")), Comparison.GreaterThan);
assert.strictEqual(new Version("1.0.0-b.0.a.1").compareTo(new Version("1.0.0-a.0.b.1")), Comparison.GreaterThan);
// https://semver.org/#spec-item-11
// > Build metadata does not figure into precedence
assert.strictEqual(new Version("1.0.0+build").compareTo(new Version("1.0.0")), Comparison.EqualTo);
});
it("increment", () => {
assertVersion(new Version(1, 2, 3, "pre.4", "build.5").increment("major"), [2, 0, 0]);
assertVersion(new Version(1, 2, 3, "pre.4", "build.5").increment("minor"), [1, 3, 0]);
assertVersion(new Version(1, 2, 3, "pre.4", "build.5").increment("patch"), [1, 2, 4]);
});
});
describe("VersionRange", () => {
function assertRange(rangeText: string, versionText: string, inRange = true) {
const range = new VersionRange(rangeText);
const version = new Version(versionText);
assert.strictEqual(range.test(version), inRange, `Expected version '${version}' ${inRange ? `to be` : `to not be`} in range '${rangeText}' (${range})`);
}
theory("comparators", assertRange, [
["", "1.0.0"],
["*", "1.0.0"],
["1", "1.0.0"],
["1", "2.0.0", false],
["1.0", "1.0.0"],
["1.0", "1.1.0", false],
["1.0.0", "1.0.0"],
["1.0.0", "1.0.1", false],
["1.*", "1.0.0"],
["1.*", "2.0.0", false],
["1.x", "1.0.0"],
["1.x", "2.0.0", false],
["=1", "1.0.0"],
["=1", "1.1.0"],
["=1", "1.0.1"],
["=1.0", "1.0.0"],
["=1.0", "1.0.1"],
["=1.0.0", "1.0.0"],
["=*", "0.0.0"],
["=*", "1.0.0"],
[">1", "2"],
[">1.0", "1.1"],
[">1.0.0", "1.0.1"],
[">1.0.0", "1.0.1-pre"],
[">*", "0.0.0", false],
[">*", "1.0.0", false],
[">=1", "1.0.0"],
[">=1.0", "1.0.0"],
[">=1.0.0", "1.0.0"],
[">=1.0.0", "1.0.1-pre"],
[">=*", "0.0.0"],
[">=*", "1.0.0"],
["<2", "1.0.0"],
["<2.1", "2.0.0"],
["<2.0.1", "2.0.0"],
["<2.0.0", "2.0.0-pre"],
["<*", "0.0.0", false],
["<*", "1.0.0", false],
["<=2", "2.0.0"],
["<=2.1", "2.1.0"],
["<=2.0.1", "2.0.1"],
["<=*", "0.0.0"],
["<=*", "1.0.0"],
]);
theory("conjunctions", assertRange, [
[">1.0.0 <2.0.0", "1.0.1"],
[">1.0.0 <2.0.0", "2.0.0", false],
[">1.0.0 <2.0.0", "1.0.0", false],
[">1 >2", "3.0.0"],
]);
theory("disjunctions", assertRange, [
[">=1.0.0 <2.0.0 || >=3.0.0 <4.0.0", "1.0.0"],
[">=1.0.0 <2.0.0 || >=3.0.0 <4.0.0", "2.0.0", false],
[">=1.0.0 <2.0.0 || >=3.0.0 <4.0.0", "3.0.0"],
]);
theory("hyphen", assertRange, [
["1.0.0 - 2.0.0", "1.0.0"],
["1.0.0 - 2.0.0", "2.0.0"],
["1.0.0 - 2.0.0", "3.0.0", false],
]);
theory("tilde", assertRange, [
["~0", "0.0.0"],
["~0", "0.1.0"],
["~0", "0.1.2"],
["~0", "0.1.9"],
["~0", "1.0.0", false],
["~0.1", "0.1.0"],
["~0.1", "0.1.2"],
["~0.1", "0.1.9"],
["~0.1", "0.2.0", false],
["~0.1.2", "0.1.2"],
["~0.1.2", "0.1.9"],
["~0.1.2", "0.2.0", false],
["~1", "1.0.0"],
["~1", "1.2.0"],
["~1", "1.2.3"],
["~1", "1.2.0"],
["~1", "1.2.3"],
["~1", "0.0.0", false],
["~1", "2.0.0", false],
["~1.2", "1.2.0"],
["~1.2", "1.2.3"],
["~1.2", "1.1.0", false],
["~1.2", "1.3.0", false],
["~1.2.3", "1.2.3"],
["~1.2.3", "1.2.9"],
["~1.2.3", "1.1.0", false],
["~1.2.3", "1.3.0", false],
]);
theory("caret", assertRange, [
["^0", "0.0.0"],
["^0", "0.1.0"],
["^0", "0.9.0"],
["^0", "0.1.2"],
["^0", "0.1.9"],
["^0", "1.0.0", false],
["^0.1", "0.1.0"],
["^0.1", "0.1.2"],
["^0.1", "0.1.9"],
["^0.1.2", "0.1.2"],
["^0.1.2", "0.1.9"],
["^0.1.2", "0.0.0", false],
["^0.1.2", "0.2.0", false],
["^0.1.2", "1.0.0", false],
["^1", "1.0.0"],
["^1", "1.2.0"],
["^1", "1.2.3"],
["^1", "1.9.0"],
["^1", "0.0.0", false],
["^1", "2.0.0", false],
["^1.2", "1.2.0"],
["^1.2", "1.2.3"],
["^1.2", "1.9.0"],
["^1.2", "1.1.0", false],
["^1.2", "2.0.0", false],
["^1.2.3", "1.2.3"],
["^1.2.3", "1.9.0"],
["^1.2.3", "1.2.2", false],
["^1.2.3", "2.0.0", false],
]);
});
});
}
+2 -2
View File
@@ -502,7 +502,7 @@ namespace ts.server {
typingsInstaller: undefined!, // TODO: GH#18217
byteLength: Utils.byteLength,
hrtime: process.hrtime,
logger: projectSystem.nullLogger,
logger: projectSystem.createHasErrorMessageLogger().logger,
canUseEvents: true
});
this.addProtocolHandler(this.customHandler, () => {
@@ -570,7 +570,7 @@ namespace ts.server {
typingsInstaller: undefined!, // TODO: GH#18217
byteLength: Utils.byteLength,
hrtime: process.hrtime,
logger: projectSystem.nullLogger,
logger: projectSystem.createHasErrorMessageLogger().logger,
canUseEvents: true
});
this.addProtocolHandler("echo", (req: protocol.Request) => ({
+1 -1
View File
@@ -20,7 +20,7 @@ namespace ts {
const newLineCharacter = getNewLineCharacter(printerOptions);
function getRuleProvider(placeOpenBraceOnNewLineForFunctions: boolean): formatting.FormatContext {
return formatting.getFormatContext(placeOpenBraceOnNewLineForFunctions ? { ...testFormatOptions, placeOpenBraceOnNewLineForFunctions: true } : testFormatOptions);
return formatting.getFormatContext(placeOpenBraceOnNewLineForFunctions ? { ...testFormatSettings, placeOpenBraceOnNewLineForFunctions: true } : testFormatSettings);
}
// validate that positions that were recovered from the printed text actually match positions that will be created if the same text is parsed.

Some files were not shown because too many files have changed in this diff Show More