[Transforms] Merge master 06/06/2016 (#8991)

* Remove check narrowing only certain types, add test showing issues with this

* string literal case test

* Reconcile fix with CFA work

* Defaultable -> NotNarrowable to align with use

* Missed a defaultable in comments

* Add test for narrowing to unions of string literals

* Rewrite isInStringLiteral to accomodate for unterminated strings

* Refactor signatureHelp to expose helper functions

* Add support for completion in string literals

* Remove unused check

* Use const instead of let

* Fix error

* Formatting changes

* Use shorthand properties

* Add failing test for #8738

* Sort baseline reference identifier by name

* Detects assignment to internal module export clause, fixes #8738

* add SharedArrayBuffer

fix

* Factor out assignment op check

* Add test for composite assignment

* Factor out the behaviour and handles x++ and ++x

* Handles ES3 default as identifier name

* Fix missing else statement

* isNameOfExportedDeclarationInNonES6Module

* Reorder options alphabetically

* Mark diagnostics, and skipDefaultLibCheck as internal

* Allow an import of "foo.js" to be matched by a file "foo.ts"

* Improve loadModuleFromFile code

* Respond to PR comments

* Respond to more PR comments

* Fix test

* Actually merge from master

* Revert to old tryLoad implementation

* Run fixupParentReferences when parsing isolated jsDocComment

* initial revision of unit test support for project system in tsserver

* Allow wildcard ("*") patterns in ambient module declarations

* Add non-widening forms of null and undefined

* Create separate control flows for property declarations with initializers

* Add regression test

* Allow trailing commas in function parameter and argument lists

* Add tests

* Remove unused variable

* Add null check and CR feedback

* Support shorthand ambient module declarations

* Revert "Merge pull request #7235 from weswigham/narrow-all-types"

This reverts commit ef0f6c8fe4, reversing
changes made to 9f087cb62a.

* reuse the fixupParentReferences function

* Improve typing of && operator with --strictNullChecks

* Add test

* Respond to PR comments

* Respond to PR comments

* Add merging tests

* Use a function `stringify` to simplify calls to `JSON.stringify(xyz, undefined, 2)`

* Update tests

* Fix mistake

* Include indent in navigation bar protocol

Previously navbar01 test had indents when run in the browser but not when run from node. Now they run the same.

* Remove unnecessary restrictions in property access narrowing

* Fix fourslash test

* Add regression test

* Consider property declarations to be control flow containers

* Adding regression test

* Remove restriction on --target es5 and --module es6

* change type definition for Object.create

* Fix signature help

* Add "implicit any" warning for shorthand ambient modules

* Remove trailing whitespace

* Support using string values in enums for CompilerOptions in transpile methods

* Remove trailing whitespace in jakefile

* Make `jake runtests-browser` support test regexes with spaces

For example: `jake runtests-browser t="transpile .js files"` now works.

* Add another test

* factor out isJsxOrTsxExtension

* Move to a conformance test

* Revert "Revert "Merge pull request #7235 from weswigham/narrow-all-types""

This reverts commit fc3e040c51.

* Use inclusive flag, as originally done, but include almost everything

* Add additional tests

* Respond to PR comments

* Fix typo

* add tests for tsserver project system

* Fix test

* Allow case comparison to undefined and null in strict null checking mode

* Remove incorrectly added tests

* check if moduleResolution when verifying that program can be reused

* more tests for module resolution change and exclude

* Fix linting issues

* Merge JSDoc of assignments from function expressions

* Allow nested assignments in type guards

* Add tests

* Improve order of parameter's merged jsdoc

* Force LF newlines for LKG builds/non debug builds
Fixes 6630

* Create intersection types in type guards for unrelated types

* Split commentsFunction test into expr/decl

And renumber.

* Remove TODO comments

* Accept new baselines

* Add tests

* Remove comments

* Fix test helper

* Recognize relative path using in outDir property (#9025)

* Recognize relative path using in outDir property

* Add projects tests

* Add project .json files

* Update baselines

* Add comments

* Add test case

The test passes in 1.8 and fails in master.

* Return trace when exception happens

* Remove Long-Done TODO

AFAIK, the harness sources have been concatenated into `run.js` for as long as I've known. This stops executing them twice (and in turn makes debugging tests much easier, since you no longer have to debug into eval'd code).

* Allow primitive type guards with typeof on right

Previously, only type guards of the form `typeof x === 'string'` were
allowed. Now you can write `'string' === typeof x`.

* Primitive type guards are now order independent

* Fix comments in tests

* Add handleing for classes

* Add more tests for target=es5 module=es6

* addExportToArgumentListKind

* Accept baseline

* Add more tests

* wip-fixing transforms

* Adds progress indicators to the runtests-parallel build task.

* Fixed typo

* Fix comment

* Add test for out-of-range error

* Use proper method of not resolving alias

* Fix module loading error

(commandLineOptions_stringToEnum would be undefined if optionDeclarations wasn't loaded yet)

* Port 8739

* Update tests

* Update baselines

* Contextually type return statement in async function

* Remove stale files

* Undo change

* Improve perf

* Improve tests

* Fix sourcemaps for debugging tests

* Allow --sourceRoot with --inlineSources option
Fixes #8445

* this in parameter initializers resolves to class

Accept baselines now that the test passes.

* Add tests for more kinds of import/export

* Fix7334 Disallow async in functionExpression and ArrowFunction (#9062)

* Error when using async modifier in function-expression and arrow-function when target es5

* Add tests and baselines

* Resolve function-this in parameter initialisers when explicitly provided

* Allow null/undefined guard with null/undefined on left

Also add a test with baselines.

* Code review comments

* Update more diagnostic messages ES6->2015

Fix #8996 CC @mhegazy.

* Fixes an issue with runtests-parallel when global mocha is not installed.

* Update LKG

* Add tests

* fix baselines

* Recommend runtests-parallel in CONTRIBUTING

* Only inlineSourceMap when debugging through jake-browser (#9080)

* Only inlineSourceMap when debugging through jake-browser

* Address PR: fix typo in opt's property

* Manually port tests from PR 8470

* minor fix: add missing return clause

* Support using string values in enums for CompilerOptions in transpile methods

* Support using string values in enums for CompilerOptions in transpile methods

# Conflicts:
#	tests/cases/unittests/transpile.ts

* Fix test helper

* Add test for out-of-range error

* Fix module loading error

(commandLineOptions_stringToEnum would be undefined if optionDeclarations wasn't loaded yet)

* Use camel-case instead of snake-case (#9134)

* Manually add tests for PR 8988

* Allow wildcard ("*") patterns in ambient module declarations

* Respond to PR comments

* Add another test

* Improve perf

* Improve tests

* Update baseline from merging with master

* Address PR comment

* Update baseline

* Refactor how we retrieve binding-name cache in module transformer

* Temporary accept so we get a clean run-tests result
This commit is contained in:
Yui
2016-06-14 11:36:57 -07:00
committed by GitHub
parent d147eed1e4
commit f235bf7db8
406 changed files with 283334 additions and 272630 deletions
+40 -9
View File
@@ -51,7 +51,8 @@ namespace ts {
return state;
}
else if (node.kind === SyntaxKind.ModuleDeclaration) {
return getModuleInstanceState((<ModuleDeclaration>node).body);
const body = (<ModuleDeclaration>node).body;
return body ? getModuleInstanceState(body) : ModuleInstanceState.Instantiated;
}
else {
return ModuleInstanceState.Instantiated;
@@ -630,10 +631,11 @@ namespace ts {
case SyntaxKind.ExclamationEqualsToken:
case SyntaxKind.EqualsEqualsEqualsToken:
case SyntaxKind.ExclamationEqualsEqualsToken:
if (isNarrowingExpression(expr.left) && (expr.right.kind === SyntaxKind.NullKeyword || expr.right.kind === SyntaxKind.Identifier)) {
if ((isNarrowingExpression(expr.left) && (expr.right.kind === SyntaxKind.NullKeyword || expr.right.kind === SyntaxKind.Identifier)) ||
(isNarrowingExpression(expr.right) && (expr.left.kind === SyntaxKind.NullKeyword || expr.left.kind === SyntaxKind.Identifier))) {
return true;
}
if (expr.left.kind === SyntaxKind.TypeOfExpression && isNarrowingExpression((<TypeOfExpression>expr.left).expression) && expr.right.kind === SyntaxKind.StringLiteral) {
if (isTypeOfNarrowingBinaryExpression(expr)) {
return true;
}
return false;
@@ -645,6 +647,20 @@ namespace ts {
return false;
}
function isTypeOfNarrowingBinaryExpression(expr: BinaryExpression) {
let typeOf: Expression;
if (expr.left.kind === SyntaxKind.StringLiteral) {
typeOf = expr.right;
}
else if (expr.right.kind === SyntaxKind.StringLiteral) {
typeOf = expr.left;
}
else {
typeOf = undefined;
}
return typeOf && typeOf.kind === SyntaxKind.TypeOfExpression && isNarrowingExpression((<TypeOfExpression>typeOf).expression);
}
function createBranchLabel(): FlowLabel {
return {
flags: FlowFlags.BranchLabel,
@@ -1204,9 +1220,9 @@ namespace ts {
lastContainer = next;
}
function declareSymbolAndAddToSymbolTable(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags): void {
function declareSymbolAndAddToSymbolTable(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags): Symbol {
// Just call this directly so that the return type of this function stays "void".
declareSymbolAndAddToSymbolTableWorker(node, symbolFlags, symbolExcludes);
return declareSymbolAndAddToSymbolTableWorker(node, symbolFlags, symbolExcludes);
}
function declareSymbolAndAddToSymbolTableWorker(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags): Symbol {
@@ -1279,7 +1295,7 @@ namespace ts {
function hasExportDeclarations(node: ModuleDeclaration | SourceFile): boolean {
const body = node.kind === SyntaxKind.SourceFile ? node : (<ModuleDeclaration>node).body;
if (body.kind === SyntaxKind.SourceFile || body.kind === SyntaxKind.ModuleBlock) {
if (body && (body.kind === SyntaxKind.SourceFile || body.kind === SyntaxKind.ModuleBlock)) {
for (const stat of (<Block>body).statements) {
if (stat.kind === SyntaxKind.ExportDeclaration || stat.kind === SyntaxKind.ExportAssignment) {
return true;
@@ -1310,7 +1326,22 @@ namespace ts {
declareSymbolAndAddToSymbolTable(node, SymbolFlags.NamespaceModule, SymbolFlags.NamespaceModuleExcludes);
}
else {
declareSymbolAndAddToSymbolTable(node, SymbolFlags.ValueModule, SymbolFlags.ValueModuleExcludes);
let pattern: Pattern | undefined;
if (node.name.kind === SyntaxKind.StringLiteral) {
const text = (<StringLiteral>node.name).text;
if (hasZeroOrOneAsteriskCharacter(text)) {
pattern = tryParsePattern(text);
}
else {
errorOnFirstToken(node.name, Diagnostics.Pattern_0_can_have_at_most_one_Asterisk_character, text);
}
}
const symbol = declareSymbolAndAddToSymbolTable(node, SymbolFlags.ValueModule, SymbolFlags.ValueModuleExcludes);
if (pattern) {
(file.patternAmbientModules || (file.patternAmbientModules = [])).push({ pattern, symbol });
}
}
}
else {
@@ -2091,10 +2122,10 @@ namespace ts {
checkStrictModeFunctionName(<FunctionDeclaration>node);
if (inStrictMode) {
checkStrictModeFunctionDeclaration(node);
return bindBlockScopedDeclaration(node, SymbolFlags.Function, SymbolFlags.FunctionExcludes);
bindBlockScopedDeclaration(node, SymbolFlags.Function, SymbolFlags.FunctionExcludes);
}
else {
return declareSymbolAndAddToSymbolTable(<Declaration>node, SymbolFlags.Function, SymbolFlags.FunctionExcludes);
declareSymbolAndAddToSymbolTable(<Declaration>node, SymbolFlags.Function, SymbolFlags.FunctionExcludes);
}
}
+164 -99
View File
@@ -138,6 +138,12 @@ namespace ts {
const enumNumberIndexInfo = createIndexInfo(stringType, /*isReadonly*/ true);
const globals: SymbolTable = {};
/**
* List of every ambient module with a "*" wildcard.
* Unlike other ambient modules, these can't be stored in `globals` because symbol tables only deal with exact matches.
* This is only used if there is no exact match.
*/
let patternAmbientModules: PatternAmbientModule[];
let getGlobalESSymbolConstructorSymbol: () => Symbol;
@@ -984,7 +990,9 @@ namespace ts {
const moduleSymbol = resolveExternalModuleName(node, (<ImportDeclaration>node.parent).moduleSpecifier);
if (moduleSymbol) {
const exportDefaultSymbol = moduleSymbol.exports["export="] ?
const exportDefaultSymbol = isShorthandAmbientModule(moduleSymbol.valueDeclaration) ?
moduleSymbol :
moduleSymbol.exports["export="] ?
getPropertyOfType(getTypeOfSymbol(moduleSymbol.exports["export="]), "default") :
resolveSymbol(moduleSymbol.exports["default"]);
@@ -1058,6 +1066,10 @@ namespace ts {
if (targetSymbol) {
const name = specifier.propertyName || specifier.name;
if (name.text) {
if (isShorthandAmbientModule(moduleSymbol.valueDeclaration)) {
return moduleSymbol;
}
let symbolFromVariable: Symbol;
// First check if module was specified with "export=". If so, get the member from the resolved type
if (moduleSymbol && moduleSymbol.exports && moduleSymbol.exports["export="]) {
@@ -1283,6 +1295,14 @@ namespace ts {
}
return undefined;
}
if (patternAmbientModules) {
const pattern = findBestPatternMatch(patternAmbientModules, _ => _.pattern, moduleName);
if (pattern) {
return getMergedSymbol(pattern.symbol);
}
}
if (moduleNotFoundError) {
// report errors only if it was requested
error(moduleReferenceLiteral, moduleNotFoundError, moduleName);
@@ -2842,7 +2862,7 @@ namespace ts {
}
// In strict null checking mode, if a default value of a non-undefined type is specified, remove
// undefined from the final type.
if (strictNullChecks && declaration.initializer && !(getNullableKind(checkExpressionCached(declaration.initializer)) & TypeFlags.Undefined)) {
if (strictNullChecks && declaration.initializer && !(getCombinedTypeFlags(checkExpressionCached(declaration.initializer)) & TypeFlags.Undefined)) {
type = getTypeWithFacts(type, TypeFacts.NEUndefined);
}
return type;
@@ -2885,7 +2905,7 @@ namespace ts {
}
function addOptionality(type: Type, optional: boolean): Type {
return strictNullChecks && optional ? addNullableKind(type, TypeFlags.Undefined) : type;
return strictNullChecks && optional ? addTypeKind(type, TypeFlags.Undefined) : type;
}
// Return the inferred type for a variable, parameter, or property declaration
@@ -3218,9 +3238,14 @@ namespace ts {
function getTypeOfFuncClassEnumModule(symbol: Symbol): Type {
const links = getSymbolLinks(symbol);
if (!links.type) {
const type = createObjectType(TypeFlags.Anonymous, symbol);
links.type = strictNullChecks && symbol.flags & SymbolFlags.Optional ?
addNullableKind(type, TypeFlags.Undefined) : type;
if (symbol.valueDeclaration.kind === SyntaxKind.ModuleDeclaration && isShorthandAmbientModule(<ModuleDeclaration>symbol.valueDeclaration)) {
links.type = anyType;
}
else {
const type = createObjectType(TypeFlags.Anonymous, symbol);
links.type = strictNullChecks && symbol.flags & SymbolFlags.Optional ?
addTypeKind(type, TypeFlags.Undefined) : type;
}
}
return links.type;
}
@@ -6744,7 +6769,7 @@ namespace ts {
return getUnionType(types);
}
const supertype = forEach(primaryTypes, t => isSupertypeOfEach(t, primaryTypes) ? t : undefined);
return supertype && addNullableKind(supertype, getCombinedFlagsOfTypes(types) & TypeFlags.Nullable);
return supertype && addTypeKind(supertype, getCombinedFlagsOfTypes(types) & TypeFlags.Nullable);
}
function reportNoCommonSupertypeError(types: Type[], errorLocation: Node, errorMessageChainHead: DiagnosticMessageChain): void {
@@ -6815,28 +6840,22 @@ namespace ts {
return !!(type.flags & TypeFlags.Tuple);
}
function getNullableKind(type: Type): TypeFlags {
let flags = type.flags;
if (flags & TypeFlags.Union) {
for (const t of (type as UnionType).types) {
flags |= t.flags;
}
}
return flags & TypeFlags.Nullable;
function getCombinedTypeFlags(type: Type): TypeFlags {
return type.flags & TypeFlags.Union ? getCombinedFlagsOfTypes((<UnionType>type).types) : type.flags;
}
function addNullableKind(type: Type, kind: TypeFlags): Type {
if ((getNullableKind(type) & kind) !== kind) {
const types = [type];
if (kind & TypeFlags.Undefined) {
types.push(undefinedType);
}
if (kind & TypeFlags.Null) {
types.push(nullType);
}
type = getUnionType(types);
function addTypeKind(type: Type, kind: TypeFlags) {
if ((getCombinedTypeFlags(type) & kind) === kind) {
return type;
}
return type;
const types = [type];
if (kind & TypeFlags.String) types.push(stringType);
if (kind & TypeFlags.Number) types.push(numberType);
if (kind & TypeFlags.Boolean) types.push(booleanType);
if (kind & TypeFlags.Void) types.push(voidType);
if (kind & TypeFlags.Undefined) types.push(undefinedType);
if (kind & TypeFlags.Null) types.push(nullType);
return getUnionType(types);
}
function getNonNullableType(type: Type): Type {
@@ -7660,12 +7679,27 @@ namespace ts {
getInitialTypeOfBindingElement(<BindingElement>node);
}
function getReferenceFromExpression(node: Expression): Expression {
switch (node.kind) {
case SyntaxKind.ParenthesizedExpression:
return getReferenceFromExpression((<ParenthesizedExpression>node).expression);
case SyntaxKind.BinaryExpression:
switch ((<BinaryExpression>node).operatorToken.kind) {
case SyntaxKind.EqualsToken:
return getReferenceFromExpression((<BinaryExpression>node).left);
case SyntaxKind.CommaToken:
return getReferenceFromExpression((<BinaryExpression>node).right);
}
}
return node;
}
function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, includeOuterFunctions: boolean) {
let key: string;
if (!reference.flowNode || assumeInitialized && !(declaredType.flags & TypeFlags.Narrowable)) {
return declaredType;
}
const initialType = assumeInitialized ? declaredType : addNullableKind(declaredType, TypeFlags.Undefined);
const initialType = assumeInitialized ? declaredType : addTypeKind(declaredType, TypeFlags.Undefined);
const visitedFlowStart = visitedFlowCount;
const result = getTypeAtFlowNode(reference.flowNode);
visitedFlowCount = visitedFlowStart;
@@ -7859,10 +7893,11 @@ namespace ts {
case SyntaxKind.ExclamationEqualsToken:
case SyntaxKind.EqualsEqualsEqualsToken:
case SyntaxKind.ExclamationEqualsEqualsToken:
if (isNullOrUndefinedLiteral(expr.right)) {
if (isNullOrUndefinedLiteral(expr.left) || isNullOrUndefinedLiteral(expr.right)) {
return narrowTypeByNullCheck(type, expr, assumeTrue);
}
if (expr.left.kind === SyntaxKind.TypeOfExpression && expr.right.kind === SyntaxKind.StringLiteral) {
if (expr.left.kind === SyntaxKind.TypeOfExpression && expr.right.kind === SyntaxKind.StringLiteral ||
expr.left.kind === SyntaxKind.StringLiteral && expr.right.kind === SyntaxKind.TypeOfExpression) {
return narrowTypeByTypeof(type, expr, assumeTrue);
}
break;
@@ -7875,18 +7910,20 @@ namespace ts {
}
function narrowTypeByNullCheck(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
// We have '==', '!=', '===', or '!==' operator with 'null' or 'undefined' on the right
// We have '==', '!=', '===', or '!==' operator with 'null' or 'undefined' on one side
const operator = expr.operatorToken.kind;
const nullLike = isNullOrUndefinedLiteral(expr.left) ? expr.left : expr.right;
const narrowed = isNullOrUndefinedLiteral(expr.left) ? expr.right : expr.left;
if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) {
assumeTrue = !assumeTrue;
}
if (!strictNullChecks || !isMatchingReference(reference, expr.left)) {
if (!strictNullChecks || !isMatchingReference(reference, getReferenceFromExpression(narrowed))) {
return type;
}
const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken;
const facts = doubleEquals ?
assumeTrue ? TypeFacts.EQUndefinedOrNull : TypeFacts.NEUndefinedOrNull :
expr.right.kind === SyntaxKind.NullKeyword ?
nullLike.kind === SyntaxKind.NullKeyword ?
assumeTrue ? TypeFacts.EQNull : TypeFacts.NENull :
assumeTrue ? TypeFacts.EQUndefined : TypeFacts.NEUndefined;
return getTypeWithFacts(type, facts);
@@ -7895,12 +7932,12 @@ namespace ts {
function narrowTypeByTypeof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
// We have '==', '!=', '====', or !==' operator with 'typeof xxx' on the left
// and string literal on the right
const left = <TypeOfExpression>expr.left;
const right = <LiteralExpression>expr.right;
if (!isMatchingReference(reference, left.expression)) {
const narrowed = getReferenceFromExpression((<TypeOfExpression>(expr.left.kind === SyntaxKind.TypeOfExpression ? expr.left : expr.right)).expression);
const literal = <LiteralExpression>(expr.right.kind === SyntaxKind.StringLiteral ? expr.right : expr.left);
if (!isMatchingReference(reference, narrowed)) {
// For a reference of the form 'x.y', a 'typeof x === ...' type guard resets the
// narrowed type of 'y' to its declared type.
if (containsMatchingReference(reference, left.expression)) {
if (containsMatchingReference(reference, narrowed)) {
return declaredType;
}
return type;
@@ -7913,22 +7950,23 @@ namespace ts {
// We narrow a non-union type to an exact primitive type if the non-union type
// is a supertype of that primtive type. For example, type 'any' can be narrowed
// to one of the primitive types.
const targetType = getProperty(typeofTypesByName, right.text);
const targetType = getProperty(typeofTypesByName, literal.text);
if (targetType && isTypeSubtypeOf(targetType, type)) {
return targetType;
}
}
const facts = assumeTrue ?
getProperty(typeofEQFacts, right.text) || TypeFacts.TypeofEQHostObject :
getProperty(typeofNEFacts, right.text) || TypeFacts.TypeofNEHostObject;
getProperty(typeofEQFacts, literal.text) || TypeFacts.TypeofEQHostObject :
getProperty(typeofNEFacts, literal.text) || TypeFacts.TypeofNEHostObject;
return getTypeWithFacts(type, facts);
}
function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
if (!isMatchingReference(reference, expr.left)) {
const left = getReferenceFromExpression(expr.left);
if (!isMatchingReference(reference, left)) {
// For a reference of the form 'x.y', an 'x instanceof T' type guard resets the
// narrowed type of 'y' to its declared type.
if (containsMatchingReference(reference, expr.left)) {
if (containsMatchingReference(reference, left)) {
return declaredType;
}
return type;
@@ -7995,7 +8033,7 @@ namespace ts {
const targetType = type.flags & TypeFlags.TypeParameter ? getApparentType(type) : type;
return isTypeAssignableTo(candidate, targetType) ? candidate :
isTypeAssignableTo(type, candidate) ? type :
neverType;
getIntersectionType([type, candidate]);
}
function narrowTypeByTypePredicate(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type {
@@ -8089,13 +8127,22 @@ namespace ts {
return expression;
}
function getControlFlowContainer(node: Node): Node {
while (true) {
node = node.parent;
if (isFunctionLike(node) || node.kind === SyntaxKind.ModuleBlock || node.kind === SyntaxKind.SourceFile || node.kind === SyntaxKind.PropertyDeclaration) {
return node;
}
}
}
function isDeclarationIncludedInFlow(reference: Node, declaration: Declaration, includeOuterFunctions: boolean) {
const declarationContainer = getContainingFunctionOrModule(declaration);
let container = getContainingFunctionOrModule(reference);
const declarationContainer = getControlFlowContainer(declaration);
let container = getControlFlowContainer(reference);
while (container !== declarationContainer &&
(container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.ArrowFunction) &&
(includeOuterFunctions || getImmediatelyInvokedFunctionExpression(<FunctionExpression>container))) {
container = getContainingFunctionOrModule(container);
container = getControlFlowContainer(container);
}
return container === declarationContainer;
}
@@ -8161,7 +8208,7 @@ namespace ts {
getRootDeclaration(declaration).kind === SyntaxKind.Parameter || isInAmbientContext(declaration) ||
!isDeclarationIncludedInFlow(node, declaration, includeOuterFunctions);
const flowType = getFlowTypeOfReference(node, type, assumeInitialized, includeOuterFunctions);
if (!assumeInitialized && !(getNullableKind(type) & TypeFlags.Undefined) && getNullableKind(flowType) & TypeFlags.Undefined) {
if (!assumeInitialized && !(getCombinedTypeFlags(type) & TypeFlags.Undefined) && getCombinedTypeFlags(flowType) & TypeFlags.Undefined) {
error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol));
// Return the declared type to reduce follow-on errors
return type;
@@ -8381,7 +8428,10 @@ namespace ts {
if (needToCaptureLexicalThis) {
captureLexicalThis(node, container);
}
if (isFunctionLike(container)) {
if (isFunctionLike(container) &&
(!isInParameterInitializerBeforeContainingFunction(node) || getFunctionLikeThisParameter(container))) {
// Note: a parameter initializer should refer to class-this unless function-this is explicitly annotated.
// If this is a function in a JS file, it might be a class method. Check if it's the RHS
// of a x.prototype.y = function [name]() { .... }
if (container.kind === SyntaxKind.FunctionExpression &&
@@ -8735,6 +8785,16 @@ namespace ts {
function getContextualTypeForReturnExpression(node: Expression): Type {
const func = getContainingFunction(node);
if (isAsyncFunctionLike(func)) {
const contextualReturnType = getContextualReturnType(func);
if (contextualReturnType) {
return getPromisedType(contextualReturnType);
}
return undefined;
}
if (func && !func.asteriskToken) {
return getContextualReturnType(func);
}
@@ -9947,7 +10007,7 @@ namespace ts {
function checkNonNullExpression(node: Expression | QualifiedName) {
const type = checkExpression(node);
if (strictNullChecks) {
const kind = getNullableKind(type);
const kind = getCombinedTypeFlags(type) & TypeFlags.Nullable;
if (kind) {
error(node, kind & TypeFlags.Undefined ? kind & TypeFlags.Null ?
Diagnostics.Object_is_possibly_null_or_undefined :
@@ -9993,24 +10053,14 @@ namespace ts {
}
const propType = getTypeOfSymbol(prop);
// Only compute control flow type if this is a property access expression that isn't an
// assignment target, and the referenced property was declared as a variable, property,
// accessor, or optional method.
if (node.kind !== SyntaxKind.PropertyAccessExpression || isAssignmentTarget(node) ||
!(propType.flags & TypeFlags.Union) && !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor))) {
!(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) &&
!(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)) {
return propType;
}
const leftmostNode = getLeftmostIdentifierOrThis(node);
if (!leftmostNode) {
return propType;
}
if (leftmostNode.kind === SyntaxKind.Identifier) {
const leftmostSymbol = getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(<Identifier>leftmostNode));
if (!leftmostSymbol) {
return propType;
}
const declaration = leftmostSymbol.valueDeclaration;
if (!declaration || declaration.kind !== SyntaxKind.VariableDeclaration && declaration.kind !== SyntaxKind.Parameter && declaration.kind !== SyntaxKind.BindingElement) {
return propType;
}
}
return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true, /*includeOuterFunctions*/ false);
}
@@ -10325,8 +10375,8 @@ namespace ts {
return -1;
}
function hasCorrectArity(node: CallLikeExpression, args: Expression[], signature: Signature) {
let adjustedArgCount: number; // Apparent number of arguments we will have in this call
function hasCorrectArity(node: CallLikeExpression, args: Expression[], signature: Signature, signatureHelpTrailingComma = false) {
let argCount: number; // Apparent number of arguments we will have in this call
let typeArguments: NodeArray<TypeNode>; // Type arguments (undefined if none)
let callIsIncomplete: boolean; // In incomplete call we want to be lenient when we have too few arguments
let isDecorator: boolean;
@@ -10337,7 +10387,7 @@ namespace ts {
// Even if the call is incomplete, we'll have a missing expression as our last argument,
// so we can say the count is just the arg list length
adjustedArgCount = args.length;
argCount = args.length;
typeArguments = undefined;
if (tagExpression.template.kind === SyntaxKind.TemplateExpression) {
@@ -10360,7 +10410,7 @@ namespace ts {
else if (node.kind === SyntaxKind.Decorator) {
isDecorator = true;
typeArguments = undefined;
adjustedArgCount = getEffectiveArgumentCount(node, /*args*/ undefined, signature);
argCount = getEffectiveArgumentCount(node, /*args*/ undefined, signature);
}
else {
const callExpression = <CallExpression>node;
@@ -10371,8 +10421,7 @@ namespace ts {
return signature.minArgumentCount === 0;
}
// For IDE scenarios we may have an incomplete call, so a trailing comma is tantamount to adding another argument.
adjustedArgCount = callExpression.arguments.hasTrailingComma ? args.length + 1 : args.length;
argCount = signatureHelpTrailingComma ? args.length + 1 : args.length;
// If we are missing the close paren, the call is incomplete.
callIsIncomplete = (<CallExpression>callExpression).arguments.end === callExpression.end;
@@ -10396,12 +10445,12 @@ namespace ts {
}
// Too many arguments implies incorrect arity.
if (!signature.hasRestParameter && adjustedArgCount > signature.parameters.length) {
if (!signature.hasRestParameter && argCount > signature.parameters.length) {
return false;
}
// If the call is incomplete, we should skip the lower bound check.
const hasEnoughArguments = adjustedArgCount >= signature.minArgumentCount;
const hasEnoughArguments = argCount >= signature.minArgumentCount;
return callIsIncomplete || hasEnoughArguments;
}
@@ -10973,6 +11022,11 @@ namespace ts {
let resultOfFailedInference: InferenceContext;
let result: Signature;
// If we are in signature help, a trailing comma indicates that we intend to provide another argument,
// so we will only accept overloads with arity at least 1 higher than the current number of provided arguments.
const signatureHelpTrailingComma =
candidatesOutArray && node.kind === SyntaxKind.CallExpression && (<CallExpression>node).arguments.hasTrailingComma;
// Section 4.12.1:
// if the candidate list contains one or more signatures for which the type of each argument
// expression is a subtype of each corresponding parameter type, the return type of the first
@@ -10984,14 +11038,14 @@ namespace ts {
// is just important for choosing the best signature. So in the case where there is only one
// signature, the subtype pass is useless. So skipping it is an optimization.
if (candidates.length > 1) {
result = chooseOverload(candidates, subtypeRelation);
result = chooseOverload(candidates, subtypeRelation, signatureHelpTrailingComma);
}
if (!result) {
// Reinitialize these pointers for round two
candidateForArgumentError = undefined;
candidateForTypeArgumentError = undefined;
resultOfFailedInference = undefined;
result = chooseOverload(candidates, assignableRelation);
result = chooseOverload(candidates, assignableRelation, signatureHelpTrailingComma);
}
if (result) {
return result;
@@ -11062,9 +11116,9 @@ namespace ts {
diagnostics.add(createDiagnosticForNodeFromMessageChain(node, errorInfo));
}
function chooseOverload(candidates: Signature[], relation: Map<RelationComparisonResult>) {
function chooseOverload(candidates: Signature[], relation: Map<RelationComparisonResult>, signatureHelpTrailingComma = false) {
for (const originalCandidate of candidates) {
if (!hasCorrectArity(node, args, originalCandidate)) {
if (!hasCorrectArity(node, args, originalCandidate, signatureHelpTrailingComma)) {
continue;
}
@@ -11487,7 +11541,7 @@ namespace ts {
if (strictNullChecks) {
const declaration = symbol.valueDeclaration;
if (declaration && (<VariableLikeDeclaration>declaration).initializer) {
return addNullableKind(type, TypeFlags.Undefined);
return addTypeKind(type, TypeFlags.Undefined);
}
}
return type;
@@ -12413,7 +12467,7 @@ namespace ts {
case SyntaxKind.InKeyword:
return checkInExpression(left, right, leftType, rightType);
case SyntaxKind.AmpersandAmpersandToken:
return strictNullChecks ? addNullableKind(rightType, getNullableKind(leftType)) : rightType;
return strictNullChecks ? addTypeKind(rightType, getCombinedTypeFlags(leftType) & TypeFlags.Falsy) : rightType;
case SyntaxKind.BarBarToken:
return getUnionType([getNonNullableType(leftType), rightType]);
case SyntaxKind.EqualsToken:
@@ -15109,7 +15163,7 @@ namespace ts {
// In a 'switch' statement, each 'case' expression must be of a type that is comparable
// to or from the type of the 'switch' expression.
const caseType = checkExpression(caseClause.expression);
if (!isTypeComparableTo(expressionType, caseType)) {
if (!isTypeEqualityComparableTo(expressionType, caseType)) {
// expressionType is not comparable to caseType, try the reversed check and report errors if it fails
checkTypeComparableTo(caseType, expressionType, caseClause.expression, /*headMessage*/ undefined);
}
@@ -16014,7 +16068,7 @@ namespace ts {
// - augmentation for a global scope is always applied
// - augmentation for some external module is applied if symbol for augmentation is merged (it was combined with target module).
const checkBody = isGlobalAugmentation || (getSymbolOfNode(node).flags & SymbolFlags.Merged);
if (checkBody) {
if (checkBody && node.body) {
// body of ambient external module is always a module block
for (const statement of (<ModuleBlock>node.body).statements) {
checkModuleAugmentationElement(statement, isGlobalAugmentation);
@@ -16041,7 +16095,15 @@ namespace ts {
}
}
}
checkSourceElement(node.body);
if (compilerOptions.noImplicitAny && !node.body) {
// Ambient shorthand module is an implicit any
reportImplicitAnyError(node, anyType);
}
if (node.body) {
checkSourceElement(node.body);
}
}
function checkModuleAugmentationElement(node: Node, isGlobalAugmentation: boolean): void {
@@ -16226,7 +16288,7 @@ namespace ts {
else {
if (modulekind === ModuleKind.ES6 && !isInAmbientContext(node)) {
// Import equals declaration is deprecated in es6 or above
grammarErrorOnNode(node, Diagnostics.Import_assignment_cannot_be_used_when_targeting_ECMAScript_6_modules_Consider_using_import_Asterisk_as_ns_from_mod_import_a_from_mod_import_d_from_mod_or_another_module_format_instead);
grammarErrorOnNode(node, Diagnostics.Import_assignment_cannot_be_used_when_targeting_ECMAScript_2015_modules_Consider_using_import_Asterisk_as_ns_from_mod_import_a_from_mod_import_d_from_mod_or_another_module_format_instead);
}
}
}
@@ -16312,7 +16374,7 @@ namespace ts {
if (node.isExportEquals && !isInAmbientContext(node)) {
if (modulekind === ModuleKind.ES6) {
// export assignment is not supported in es6 modules
grammarErrorOnNode(node, Diagnostics.Export_assignment_cannot_be_used_when_targeting_ECMAScript_6_modules_Consider_using_export_default_or_another_module_format_instead);
grammarErrorOnNode(node, Diagnostics.Export_assignment_cannot_be_used_when_targeting_ECMAScript_2015_modules_Consider_using_export_default_or_another_module_format_instead);
}
else if (modulekind === ModuleKind.System) {
// system modules does not support export assignment
@@ -16862,10 +16924,7 @@ namespace ts {
return getIntrinsicTagSymbol(<JsxOpeningLikeElement>entityName.parent);
}
// Include aliases in the meaning, this ensures that we do not follow aliases to where they point and instead
// return the alias symbol.
const meaning: SymbolFlags = SymbolFlags.Value | SymbolFlags.Alias;
return resolveEntityName(<Identifier>entityName, meaning);
return resolveEntityName(<Identifier>entityName, SymbolFlags.Value, /*ignoreErrors*/ false, /*dontResolveAlias*/ true);
}
else if (entityName.kind === SyntaxKind.PropertyAccessExpression) {
const symbol = getNodeLinks(entityName).resolvedSymbol;
@@ -16883,11 +16942,8 @@ namespace ts {
}
}
else if (isTypeReferenceIdentifier(<EntityName>entityName)) {
let meaning = (entityName.parent.kind === SyntaxKind.TypeReference || entityName.parent.kind === SyntaxKind.JSDocTypeReference) ? SymbolFlags.Type : SymbolFlags.Namespace;
// Include aliases in the meaning, this ensures that we do not follow aliases to where they point and instead
// return the alias symbol.
meaning |= SymbolFlags.Alias;
return resolveEntityName(<EntityName>entityName, meaning);
const meaning = (entityName.parent.kind === SyntaxKind.TypeReference || entityName.parent.kind === SyntaxKind.JSDocTypeReference) ? SymbolFlags.Type : SymbolFlags.Namespace;
return resolveEntityName(<EntityName>entityName, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/true);
}
else if (entityName.parent.kind === SyntaxKind.JsxAttribute) {
return getJsxAttributePropertySymbol(<JsxAttribute>entityName.parent);
@@ -17709,6 +17765,10 @@ namespace ts {
if (!isExternalOrCommonJsModule(file)) {
mergeSymbolTable(globals, file.locals);
}
if (file.patternAmbientModules && file.patternAmbientModules.length) {
patternAmbientModules = concatenate(patternAmbientModules, file.patternAmbientModules);
}
if (file.moduleAugmentations.length) {
(augmentations || (augmentations = [])).push(file.moduleAugmentations);
}
@@ -17839,6 +17899,8 @@ namespace ts {
case SyntaxKind.ImportEqualsDeclaration:
case SyntaxKind.ExportDeclaration:
case SyntaxKind.ExportAssignment:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
case SyntaxKind.Parameter:
break;
case SyntaxKind.FunctionDeclaration:
@@ -18070,7 +18132,7 @@ namespace ts {
function checkGrammarAsyncModifier(node: Node, asyncModifier: Node): boolean {
if (languageVersion < ScriptTarget.ES6) {
return grammarErrorOnNode(asyncModifier, Diagnostics.Async_functions_are_only_available_when_targeting_ECMAScript_6_and_higher);
return grammarErrorOnNode(asyncModifier, Diagnostics.Async_functions_are_only_available_when_targeting_ECMAScript_2015_or_higher);
}
switch (node.kind) {
@@ -18109,10 +18171,6 @@ namespace ts {
}
function checkGrammarParameterList(parameters: NodeArray<ParameterDeclaration>) {
if (checkGrammarForDisallowedTrailingComma(parameters)) {
return true;
}
let seenOptionalParameter = false;
const parameterCount = parameters.length;
@@ -18231,8 +18289,7 @@ namespace ts {
}
function checkGrammarArguments(node: CallExpression, args: NodeArray<Expression>): boolean {
return checkGrammarForDisallowedTrailingComma(args) ||
checkGrammarForOmittedArgument(node, args);
return checkGrammarForOmittedArgument(node, args);
}
function checkGrammarHeritageClause(node: HeritageClause): boolean {
@@ -18333,7 +18390,7 @@ namespace ts {
return grammarErrorOnNode(node.asteriskToken, Diagnostics.An_overload_signature_cannot_be_declared_as_a_generator);
}
if (languageVersion < ScriptTarget.ES6) {
return grammarErrorOnNode(node.asteriskToken, Diagnostics.Generators_are_only_available_when_targeting_ECMAScript_6_or_higher);
return grammarErrorOnNode(node.asteriskToken, Diagnostics.Generators_are_only_available_when_targeting_ECMAScript_2015_or_higher);
}
}
}
@@ -18557,6 +18614,14 @@ namespace ts {
}
}
function getFunctionLikeThisParameter(func: FunctionLikeDeclaration) {
if (func.parameters.length &&
func.parameters[0].name.kind === SyntaxKind.Identifier &&
(<Identifier>func.parameters[0].name).originalKeywordKind === SyntaxKind.ThisKeyword) {
return func.parameters[0];
}
}
function checkGrammarForNonSymbolComputedProperty(node: DeclarationName, message: DiagnosticMessage) {
if (isDynamicName(node)) {
return grammarErrorOnNode(node, message);
+4 -3
View File
@@ -6,7 +6,7 @@
namespace ts {
/* @internal */
export let optionDeclarations: CommandLineOption[] = [
export const optionDeclarations: CommandLineOption[] = [
{
name: "charset",
type: "string",
@@ -406,7 +406,8 @@ namespace ts {
"es2015.symbol": "lib.es2015.symbol.d.ts",
"es2015.symbol.wellknown": "lib.es2015.symbol.wellknown.d.ts",
"es2016.array.include": "lib.es2016.array.include.d.ts",
"es2017.object": "lib.es2017.object.d.ts"
"es2017.object": "lib.es2017.object.d.ts",
"es2017.sharedmemory": "lib.es2017.sharedmemory.d.ts"
},
},
description: Diagnostics.Specify_library_files_to_be_included_in_the_compilation_Colon
@@ -722,7 +723,7 @@ namespace ts {
if (outDir) {
exclude.push(outDir);
}
exclude = map(exclude, normalizeSlashes);
exclude = map(exclude, e => getNormalizedAbsolutePath(e, basePath));
const supportedExtensions = getSupportedExtensions(options);
Debug.assert(indexOf(supportedExtensions, ".ts") < indexOf(supportedExtensions, ".d.ts"), "Changed priority of extensions to pick");
+11 -2
View File
@@ -1020,13 +1020,22 @@ namespace ts {
const extensionsToRemove = [".d.ts", ".ts", ".js", ".tsx", ".jsx"];
export function removeFileExtension(path: string): string {
for (const ext of extensionsToRemove) {
if (fileExtensionIs(path, ext)) {
return path.substr(0, path.length - ext.length);
const extensionless = tryRemoveExtension(path, ext);
if (extensionless !== undefined) {
return extensionless;
}
}
return path;
}
export function tryRemoveExtension(path: string, extension: string): string {
return fileExtensionIs(path, extension) ? path.substring(0, path.length - extension.length) : undefined;
}
export function isJsxOrTsxExtension(ext: string): boolean {
return ext === ".jsx" || ext === ".tsx";
}
export interface ObjectAllocator {
getNodeConstructor(): new (kind: SyntaxKind, pos?: number, end?: number) => Node;
getSourceFileConstructor(): new (kind: SyntaxKind, pos?: number, end?: number) => SourceFile;
+15 -10
View File
@@ -854,21 +854,26 @@ namespace ts {
writeTextOfNode(currentText, node.name);
}
}
while (node.body.kind !== SyntaxKind.ModuleBlock) {
while (node.body && node.body.kind !== SyntaxKind.ModuleBlock) {
node = <ModuleDeclaration>node.body;
write(".");
writeTextOfNode(currentText, node.name);
}
const prevEnclosingDeclaration = enclosingDeclaration;
enclosingDeclaration = node;
write(" {");
writeLine();
increaseIndent();
emitLines((<ModuleBlock>node.body).statements);
decreaseIndent();
write("}");
writeLine();
enclosingDeclaration = prevEnclosingDeclaration;
if (node.body) {
enclosingDeclaration = node;
write(" {");
writeLine();
increaseIndent();
emitLines((<ModuleBlock>node.body).statements);
decreaseIndent();
write("}");
writeLine();
enclosingDeclaration = prevEnclosingDeclaration;
}
else {
write(";");
}
}
function writeTypeAliasDeclaration(node: TypeAliasDeclaration) {
+9 -9
View File
@@ -627,18 +627,14 @@
"category": "Error",
"code": 1200
},
"Import assignment cannot be used when targeting ECMAScript 6 modules. Consider using 'import * as ns from \"mod\"', 'import {a} from \"mod\"', 'import d from \"mod\"', or another module format instead.": {
"Import assignment cannot be used when targeting ECMAScript 2015 modules. Consider using 'import * as ns from \"mod\"', 'import {a} from \"mod\"', 'import d from \"mod\"', or another module format instead.": {
"category": "Error",
"code": 1202
},
"Export assignment cannot be used when targeting ECMAScript 6 modules. Consider using 'export default' or another module format instead.": {
"Export assignment cannot be used when targeting ECMAScript 2015 modules. Consider using 'export default' or another module format instead.": {
"category": "Error",
"code": 1203
},
"Cannot compile modules into 'es2015' when targeting 'ES5' or lower.": {
"category": "Error",
"code": 1204
},
"Decorators are not valid here.": {
"category": "Error",
"code": 1206
@@ -687,7 +683,7 @@
"category": "Error",
"code": 1219
},
"Generators are only available when targeting ECMAScript 6 or higher.": {
"Generators are only available when targeting ECMAScript 2015 or higher.": {
"category": "Error",
"code": 1220
},
@@ -831,7 +827,7 @@
"category": "Error",
"code": 1308
},
"Async functions are only available when targeting ECMAScript 6 and higher.": {
"Async functions are only available when targeting ECMAScript 2015 or higher.": {
"category": "Error",
"code": 1311
},
@@ -2260,7 +2256,7 @@
"category": "Error",
"code": 5047
},
"Option 'inlineSources' can only be used when either option '--inlineSourceMap' or option '--sourceMap' is provided.": {
"Option '{0} can only be used when either option '--inlineSourceMap' or option '--sourceMap' is provided.": {
"category": "Error",
"code": 5051
},
@@ -2772,6 +2768,10 @@
"category": "Error",
"code": 6131
},
"File name '{0}' has a '{1}' extension - stripping it": {
"category": "Message",
"code": 6132
},
"Variable '{0}' implicitly has an '{1}' type.": {
"category": "Error",
+8 -1
View File
@@ -5332,7 +5332,14 @@ namespace ts {
else {
node.name = parseLiteralNode(/*internName*/ true);
}
node.body = parseModuleBlock();
if (token === SyntaxKind.OpenBraceToken) {
node.body = parseModuleBlock();
}
else {
parseSemicolon();
}
return finishNode(node);
}
+112 -56
View File
@@ -90,7 +90,8 @@ namespace ts {
return compilerOptions.traceResolution && host.trace !== undefined;
}
function hasZeroOrOneAsteriskCharacter(str: string): boolean {
/* @internal */
export function hasZeroOrOneAsteriskCharacter(str: string): boolean {
let seenAsterisk = false;
for (let i = 0; i < str.length; i++) {
if (str.charCodeAt(i) === CharacterCodes.asterisk) {
@@ -491,48 +492,23 @@ namespace ts {
trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, state.compilerOptions.baseUrl, moduleName);
}
let longestMatchPrefixLength = -1;
let matchedPattern: string;
let matchedStar: string;
// string is for exact match
let matchedPattern: Pattern | string | undefined = undefined;
if (state.compilerOptions.paths) {
if (state.traceEnabled) {
trace(state.host, Diagnostics.paths_option_is_specified_looking_for_a_pattern_to_match_module_name_0, moduleName);
}
for (const key in state.compilerOptions.paths) {
const pattern: string = key;
const indexOfStar = pattern.indexOf("*");
if (indexOfStar !== -1) {
const prefix = pattern.substr(0, indexOfStar);
const suffix = pattern.substr(indexOfStar + 1);
if (moduleName.length >= prefix.length + suffix.length &&
startsWith(moduleName, prefix) &&
endsWith(moduleName, suffix)) {
// use length of prefix as betterness criteria
if (prefix.length > longestMatchPrefixLength) {
longestMatchPrefixLength = prefix.length;
matchedPattern = pattern;
matchedStar = moduleName.substr(prefix.length, moduleName.length - suffix.length);
}
}
}
else if (pattern === moduleName) {
// pattern was matched as is - no need to search further
matchedPattern = pattern;
matchedStar = undefined;
break;
}
}
matchedPattern = matchPatternOrExact(getKeys(state.compilerOptions.paths), moduleName);
}
if (matchedPattern) {
const matchedStar = typeof matchedPattern === "string" ? undefined : matchedText(matchedPattern, moduleName);
const matchedPatternText = typeof matchedPattern === "string" ? matchedPattern : patternText(matchedPattern);
if (state.traceEnabled) {
trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPattern);
trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText);
}
for (const subst of state.compilerOptions.paths[matchedPattern]) {
const path = matchedStar ? subst.replace("\*", matchedStar) : subst;
for (const subst of state.compilerOptions.paths[matchedPatternText]) {
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);
@@ -555,6 +531,75 @@ namespace ts {
}
}
/**
* patternStrings contains both pattern strings (containing "*") and regular strings.
* Return an exact match if possible, or a pattern match, or undefined.
* (These are verified by verifyCompilerOptions to have 0 or 1 "*" characters.)
*/
function matchPatternOrExact(patternStrings: string[], candidate: string): string | Pattern | undefined {
const patterns: Pattern[] = [];
for (const patternString of patternStrings) {
const pattern = tryParsePattern(patternString);
if (pattern) {
patterns.push(pattern);
}
else if (patternString === candidate) {
// pattern was matched as is - no need to search further
return patternString;
}
}
return findBestPatternMatch(patterns, _ => _, candidate);
}
function patternText({prefix, suffix}: Pattern): string {
return `${prefix}*${suffix}`;
}
/**
* Given that candidate matches pattern, returns the text matching the '*'.
* E.g.: matchedText(tryParsePattern("foo*baz"), "foobarbaz") === "bar"
*/
function matchedText(pattern: Pattern, candidate: string): string {
Debug.assert(isPatternMatch(pattern, candidate));
return candidate.substr(pattern.prefix.length, candidate.length - pattern.suffix.length);
}
/** Return the object corresponding to the best pattern to match `candidate`. */
/* @internal */
export function findBestPatternMatch<T>(values: T[], getPattern: (value: T) => Pattern, candidate: string): T | undefined {
let matchedValue: T | undefined = undefined;
// use length of prefix as betterness criteria
let longestMatchPrefixLength = -1;
for (const v of values) {
const pattern = getPattern(v);
if (isPatternMatch(pattern, candidate) && pattern.prefix.length > longestMatchPrefixLength) {
longestMatchPrefixLength = pattern.prefix.length;
matchedValue = v;
}
}
return matchedValue;
}
function isPatternMatch({prefix, suffix}: Pattern, candidate: string) {
return candidate.length >= prefix.length + suffix.length &&
startsWith(candidate, prefix) &&
endsWith(candidate, suffix);
}
/* @internal */
export function tryParsePattern(pattern: string): Pattern | undefined {
// This should be verified outside of here and a proper error thrown.
Debug.assert(hasZeroOrOneAsteriskCharacter(pattern));
const indexOfStar = pattern.indexOf("*");
return indexOfStar === -1 ? undefined : {
prefix: pattern.substr(0, indexOfStar),
suffix: pattern.substr(indexOfStar + 1)
};
}
export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations {
const containingDirectory = getDirectoryPath(containingFile);
const supportedExtensions = getSupportedExtensions(compilerOptions);
@@ -614,8 +659,25 @@ namespace ts {
* 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(candidate: string, extensions: string[], failedLookupLocation: string[], onlyRecordFailures: boolean, state: ModuleResolutionState): string {
// First try to keep/add an extension: importing "./foo.ts" can be matched by a file "./foo.ts", and "./foo" by "./foo.d.ts"
const resolvedByAddingOrKeepingExtension = loadModuleFromFileWorker(candidate, extensions, failedLookupLocation, onlyRecordFailures, state);
if (resolvedByAddingOrKeepingExtension) {
return resolvedByAddingOrKeepingExtension;
}
// Then 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)) {
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 loadModuleFromFileWorker(extensionless, extensions, failedLookupLocation, onlyRecordFailures, state);
}
}
function loadModuleFromFileWorker(candidate: string, extensions: string[], failedLookupLocation: string[], onlyRecordFailures: boolean, state: ModuleResolutionState): string {
if (!onlyRecordFailures) {
// check if containig folder exists - if it doesn't then just record failures for all supported extensions without disk probing
// check if containing folder exists - if it doesn't then just record failures for all supported extensions without disk probing
const directory = getDirectoryPath(candidate);
if (directory) {
onlyRecordFailures = !directoryProbablyExists(directory, state.host);
@@ -624,7 +686,7 @@ namespace ts {
return forEach(extensions, tryLoad);
function tryLoad(ext: string): string {
if (ext === ".tsx" && state.skipTsx) {
if (state.skipTsx && isJsxOrTsxExtension(ext)) {
return undefined;
}
const fileName = fileExtensionIs(candidate, ext) ? candidate : candidate + ext;
@@ -1139,6 +1201,7 @@ namespace ts {
// if any of these properties has changed - structure cannot be reused
const oldOptions = oldProgram.getCompilerOptions();
if ((oldOptions.module !== options.module) ||
(oldOptions.moduleResolution !== options.moduleResolution) ||
(oldOptions.noResolve !== options.noResolve) ||
(oldOptions.target !== options.target) ||
(oldOptions.noLib !== options.noLib) ||
@@ -1709,9 +1772,12 @@ namespace ts {
// The StringLiteral must specify a top - level external module name.
// Relative external module names are not permitted
// NOTE: body of ambient module is always a module block
for (const statement of (<ModuleBlock>(<ModuleDeclaration>node).body).statements) {
collectModuleReferences(statement, /*inAmbientModule*/ true);
// NOTE: body of ambient module is always a module block, if it exists
const body = <ModuleBlock>(<ModuleDeclaration>node).body;
if (body) {
for (const statement of body.statements) {
collectModuleReferences(statement, /*inAmbientModule*/ true);
}
}
}
}
@@ -2039,12 +2105,12 @@ namespace ts {
}
}
if (options.inlineSources) {
if (!options.sourceMap && !options.inlineSourceMap) {
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_inlineSources_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided));
if (!options.sourceMap && !options.inlineSourceMap) {
if (options.inlineSources) {
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided, "inlineSources"));
}
if (options.sourceRoot) {
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "sourceRoot", "inlineSources"));
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided, "sourceRoot"));
}
}
@@ -2052,14 +2118,9 @@ namespace ts {
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "out", "outFile"));
}
if (!options.sourceMap && (options.mapRoot || options.sourceRoot)) {
// Error to specify --mapRoot or --sourceRoot without mapSourceFiles
if (options.mapRoot) {
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "mapRoot", "sourceMap"));
}
if (options.sourceRoot && !options.inlineSourceMap) {
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "sourceRoot", "sourceMap"));
}
if (options.mapRoot && !options.sourceMap) {
// Error to specify --mapRoot without --sourcemap
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "mapRoot", "sourceMap"));
}
if (options.declarationDir) {
@@ -2096,11 +2157,6 @@ namespace ts {
programDiagnostics.add(createFileDiagnostic(firstExternalModuleSourceFile, span.start, span.length, Diagnostics.Cannot_use_imports_exports_or_module_augmentations_when_module_is_none));
}
// Cannot specify module gen target of es6 when below es6
if (options.module === ModuleKind.ES6 && languageVersion < ScriptTarget.ES6) {
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Cannot_compile_modules_into_es2015_when_targeting_ES5_or_lower));
}
// Cannot specify module gen that isn't amd or system with --out
if (outFile) {
if (options.module && !(options.module === ModuleKind.AMD || options.module === ModuleKind.System)) {
+1 -1
View File
@@ -2,7 +2,7 @@
namespace ts {
export type FileWatcherCallback = (fileName: string, removed?: boolean) => void;
export type DirectoryWatcherCallback = (directoryName: string) => void;
export type DirectoryWatcherCallback = (fileName: string) => void;
export interface WatchedFile {
fileName: string;
callback: FileWatcherCallback;
+86 -3
View File
@@ -26,14 +26,24 @@ namespace ts {
const languageVersion = getEmitScriptTarget(compilerOptions);
const moduleKind = getEmitModuleKind(compilerOptions);
const previousOnSubstituteNode = context.onSubstituteNode;
const previousOnEmitNode = context.onEmitNode;
context.onSubstituteNode = onSubstituteNode;
context.onEmitNode = onEmitNode;
context.enableSubstitution(SyntaxKind.Identifier);
context.enableSubstitution(SyntaxKind.BinaryExpression);
context.enableSubstitution(SyntaxKind.PrefixUnaryExpression);
context.enableSubstitution(SyntaxKind.PostfixUnaryExpression);
context.enableSubstitution(SyntaxKind.ShorthandPropertyAssignment);
context.enableEmitNotification(SyntaxKind.SourceFile);
let currentSourceFile: SourceFile;
let externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[];
let exportSpecifiers: Map<ExportSpecifier[]>;
let exportEquals: ExportAssignment;
let bindingNameExportSpecifiersMap: Map<ExportSpecifier[]>;
// Subset of exportSpecifiers that is a binding-name.
// This is to reduce amount of memory we have to keep around even after we done with module-transformer
const bindingNameExportSpecifiersForFileMap: Map<Map<ExportSpecifier[]>> = {};
let hasExportStarsToExportValues: boolean;
return transformSourceFile;
@@ -653,7 +663,14 @@ namespace ts {
}
}
else {
addExportMemberAssignments(resultStatements, name);
if (!exportEquals && exportSpecifiers && hasProperty(exportSpecifiers, name.text)) {
const sourceFileId = getOriginalNodeId(currentSourceFile);
if (!bindingNameExportSpecifiersForFileMap[sourceFileId]) {
bindingNameExportSpecifiersForFileMap[sourceFileId] = {};
}
bindingNameExportSpecifiersForFileMap[sourceFileId][name.text] = exportSpecifiers[name.text];
addExportMemberAssignments(resultStatements, name);
}
}
}
@@ -799,6 +816,17 @@ namespace ts {
return node.name ? getSynthesizedClone(node.name) : getGeneratedNameForNode(node);
}
function onEmitNode(node: Node, emit: (node: Node) => void): void {
if (node.kind === SyntaxKind.SourceFile) {
bindingNameExportSpecifiersMap = bindingNameExportSpecifiersForFileMap[getOriginalNodeId(node)];
previousOnEmitNode(node, emit);
bindingNameExportSpecifiersMap = undefined;
}
else {
previousOnEmitNode(node, emit);
}
}
/**
* Hooks node substitutions.
*
@@ -833,8 +861,14 @@ namespace ts {
}
function substituteExpression(node: Expression) {
if (isIdentifier(node)) {
return substituteExpressionIdentifier(node);
switch (node.kind) {
case SyntaxKind.Identifier:
return substituteExpressionIdentifier(<Identifier>node);
case SyntaxKind.BinaryExpression:
return substituteBinaryExpression(<BinaryExpression>node);
case SyntaxKind.PostfixUnaryExpression:
case SyntaxKind.PrefixUnaryExpression:
return substituteUnaryExpression(<PrefixUnaryExpression | PostfixUnaryExpression>node);
}
return node;
@@ -846,6 +880,55 @@ namespace ts {
|| node;
}
function substituteBinaryExpression(node: BinaryExpression): Expression {
const left = node.left;
// If the left-hand-side of the binaryExpression is an identifier and its is export through export Specifier
if (isIdentifier(left) && isAssignmentOperator(node.operatorToken.kind)) {
if (bindingNameExportSpecifiersMap && hasProperty(bindingNameExportSpecifiersMap, left.text)) {
setNodeEmitFlags(node, NodeEmitFlags.NoSubstitution);
let nestedExportAssignment: BinaryExpression;
for (const specifier of bindingNameExportSpecifiersMap[left.text]) {
nestedExportAssignment = nestedExportAssignment ?
createExportAssignment(specifier.name, nestedExportAssignment) :
createExportAssignment(specifier.name, node);
}
return nestedExportAssignment;
}
}
return node;
}
function substituteUnaryExpression(node: PrefixUnaryExpression | PostfixUnaryExpression): Expression {
// Because how the compiler only parse plusplus and minusminus to be either prefixUnaryExpression or postFixUnaryExpression depended on where they are
// We don't need to check that the operator has SyntaxKind.plusplus or SyntaxKind.minusminus
const operator = node.operator;
const operand = node.operand;
if (isIdentifier(operand) && bindingNameExportSpecifiersForFileMap) {
if (bindingNameExportSpecifiersMap && hasProperty(bindingNameExportSpecifiersMap, operand.text)) {
setNodeEmitFlags(node, NodeEmitFlags.NoSubstitution);
let transformedUnaryExpression: BinaryExpression;
if (node.kind === SyntaxKind.PostfixUnaryExpression) {
transformedUnaryExpression = createBinaryWithOperatorToken(
operand,
createNode(operator === SyntaxKind.PlusPlusToken ? SyntaxKind.PlusEqualsToken : SyntaxKind.MinusEqualsToken),
createLiteral(1),
/*location*/ node
);
// We have to set no substitution flag here to prevent visit the binary expression and substitute it again as we will preform all necessary substitution in here
setNodeEmitFlags(transformedUnaryExpression, NodeEmitFlags.NoSubstitution);
}
let nestedExportAssignment: BinaryExpression;
for (const specifier of bindingNameExportSpecifiersMap[operand.text]) {
nestedExportAssignment = nestedExportAssignment ?
createExportAssignment(specifier.name, nestedExportAssignment) :
createExportAssignment(specifier.name, transformedUnaryExpression || node);
}
return nestedExportAssignment;
}
}
return node;
}
function trySubstituteExportedName(node: Identifier) {
const emitFlags = getNodeEmitFlags(node);
if ((emitFlags & NodeEmitFlags.LocalName) === 0) {
+59 -49
View File
@@ -1363,8 +1363,8 @@ namespace ts {
// @kind(SyntaxKind.ModuleDeclaration)
export interface ModuleDeclaration extends DeclarationStatement {
name: ModuleName;
body: ModuleBlock | ModuleDeclaration;
name: Identifier | LiteralExpression;
body?: ModuleBlock | ModuleDeclaration;
}
// @kind(SyntaxKind.ModuleBlock)
@@ -1725,6 +1725,7 @@ namespace ts {
/* @internal */ resolvedTypeReferenceDirectiveNames: Map<ResolvedTypeReferenceDirective>;
/* @internal */ imports: LiteralExpression[];
/* @internal */ moduleAugmentations: LiteralExpression[];
/* @internal */ patternAmbientModules?: PatternAmbientModule[];
}
export interface ScriptReferenceHost {
@@ -2202,6 +2203,20 @@ namespace ts {
[index: string]: Symbol;
}
/** Represents a "prefix*suffix" pattern. */
/* @internal */
export interface Pattern {
prefix: string;
suffix: string;
}
/** Used to track a `declare module "foo*"`-like declaration. */
/* @internal */
export interface PatternAmbientModule {
pattern: Pattern;
symbol: Symbol;
}
/* @internal */
export const enum NodeCheckFlags {
TypeChecked = 0x00000001, // Node has been type checked
@@ -2276,6 +2291,7 @@ namespace ts {
/* @internal */
Nullable = Undefined | Null,
Falsy = String | Number | Boolean | Void | Undefined | Null,
/* @internal */
Intrinsic = Any | String | Number | Boolean | ESSymbol | Void | Undefined | Null | Never,
/* @internal */
@@ -2285,7 +2301,10 @@ namespace ts {
ObjectType = Class | Interface | Reference | Tuple | Anonymous,
UnionOrIntersection = Union | Intersection,
StructuredType = ObjectType | Union | Intersection,
Narrowable = Any | ObjectType | Union | TypeParameter,
// 'Narrowable' types are types where narrowing actually narrows.
// This *should* be every type other than null, undefined, void, and never
Narrowable = Any | StructuredType | TypeParameter | StringLike | NumberLike | Boolean | ESSymbol,
/* @internal */
RequiresWidening = ContainsWideningType | ContainsObjectLiteral,
/* @internal */
@@ -2547,85 +2566,76 @@ namespace ts {
export type CompilerOptionsValue = string | number | boolean | (string | number)[] | TsConfigOnlyOptions;
export interface CompilerOptions {
allowNonTsExtensions?: boolean;
allowJs?: boolean;
/*@internal*/ allowNonTsExtensions?: boolean;
allowSyntheticDefaultImports?: boolean;
allowUnreachableCode?: boolean;
allowUnusedLabels?: boolean;
baseUrl?: string;
charset?: string;
/* @internal */ configFilePath?: string;
declaration?: boolean;
declarationDir?: string;
diagnostics?: boolean;
/* @internal */ diagnostics?: boolean;
/*@internal*/ extendedDiagnostics?: boolean;
emitBOM?: boolean;
help?: boolean;
init?: boolean;
emitDecoratorMetadata?: boolean;
experimentalDecorators?: boolean;
forceConsistentCasingInFileNames?: boolean;
/*@internal*/help?: boolean;
/*@internal*/init?: boolean;
inlineSourceMap?: boolean;
inlineSources?: boolean;
isolatedModules?: boolean;
jsx?: JsxEmit;
reactNamespace?: string;
listFiles?: boolean;
typesSearchPaths?: string[];
lib?: string[];
/*@internal*/listEmittedFiles?: boolean;
/*@internal*/listFiles?: boolean;
locale?: string;
mapRoot?: string;
module?: ModuleKind;
moduleResolution?: ModuleResolutionKind;
newLine?: NewLineKind;
noEmit?: boolean;
noEmitHelpers?: boolean;
noEmitOnError?: boolean;
noErrorTruncation?: boolean;
noFallthroughCasesInSwitch?: boolean;
noImplicitAny?: boolean;
noImplicitReturns?: boolean;
noImplicitThis?: boolean;
noImplicitUseStrict?: boolean;
noLib?: boolean;
noResolve?: boolean;
out?: string;
outFile?: string;
outDir?: string;
outFile?: string;
paths?: PathSubstitutions;
preserveConstEnums?: boolean;
/* @internal */ pretty?: DiagnosticStyle;
project?: string;
/* @internal */ pretty?: DiagnosticStyle;
reactNamespace?: string;
removeComments?: boolean;
rootDir?: string;
rootDirs?: RootPaths;
skipLibCheck?: boolean;
skipDefaultLibCheck?: boolean;
sourceMap?: boolean;
sourceRoot?: string;
strictNullChecks?: boolean;
/* @internal */ stripInternal?: boolean;
suppressExcessPropertyErrors?: boolean;
suppressImplicitAnyIndexErrors?: boolean;
target?: ScriptTarget;
version?: boolean;
watch?: boolean;
isolatedModules?: boolean;
experimentalDecorators?: boolean;
emitDecoratorMetadata?: boolean;
moduleResolution?: ModuleResolutionKind;
allowUnusedLabels?: boolean;
allowUnreachableCode?: boolean;
noImplicitReturns?: boolean;
noFallthroughCasesInSwitch?: boolean;
forceConsistentCasingInFileNames?: boolean;
baseUrl?: string;
paths?: PathSubstitutions;
rootDirs?: RootPaths;
traceResolution?: boolean;
allowSyntheticDefaultImports?: boolean;
allowJs?: boolean;
noImplicitUseStrict?: boolean;
strictNullChecks?: boolean;
skipLibCheck?: boolean;
listEmittedFiles?: boolean;
lib?: string[];
/* @internal */ stripInternal?: boolean;
/* @internal */ useLegacyEmitter?: boolean;
// Skip checking lib.d.ts to help speed up tests.
/* @internal */ skipDefaultLibCheck?: boolean;
// Do not perform validation of output file name in transpile scenarios
/* @internal */ suppressOutputPathCheck?: boolean;
/* @internal */
// When options come from a config file, its path is recorded here
configFilePath?: string;
/* @internal */
// Path used to used to compute primary search locations
typesRoot?: string;
target?: ScriptTarget;
traceResolution?: boolean;
types?: string[];
/* @internal */ typesRoot?: string;
typesSearchPaths?: string[];
/* @internal */ useLegacyEmitter?: boolean;
/*@internal*/ version?: boolean;
/*@internal*/ watch?: boolean;
list?: string[];
[option: string]: CompilerOptionsValue | undefined;
}
+12 -12
View File
@@ -424,6 +424,11 @@ namespace ts {
((<ModuleDeclaration>node).name.kind === SyntaxKind.StringLiteral || isGlobalScopeAugmentation(<ModuleDeclaration>node));
}
export function isShorthandAmbientModule(node: Node): boolean {
// The only kind of module that can be missing a body is a shorthand ambient module.
return node.kind === SyntaxKind.ModuleDeclaration && (!(<ModuleDeclaration>node).body);
}
export function isBlockScopedContainerTopLevel(node: Node): boolean {
return node.kind === SyntaxKind.SourceFile ||
node.kind === SyntaxKind.ModuleDeclaration ||
@@ -685,9 +690,11 @@ namespace ts {
}
export function getJsDocCommentsFromText(node: Node, text: string) {
const commentRanges = (node.kind === SyntaxKind.Parameter || node.kind === SyntaxKind.TypeParameter) ?
concatenate(getTrailingCommentRanges(text, node.pos),
getLeadingCommentRanges(text, node.pos)) :
const commentRanges = (node.kind === SyntaxKind.Parameter ||
node.kind === SyntaxKind.TypeParameter ||
node.kind === SyntaxKind.FunctionExpression ||
node.kind === SyntaxKind.ArrowFunction) ?
concatenate(getTrailingCommentRanges(text, node.pos), getLeadingCommentRanges(text, node.pos)) :
getLeadingCommentRangesOfNodeFromText(node, text);
return filter(commentRanges, isJsDocComment);
@@ -906,6 +913,8 @@ namespace ts {
case SyntaxKind.ConstructorType:
return true;
}
return false;
}
export function introducesArgumentsExoticObject(node: Node) {
@@ -963,15 +972,6 @@ namespace ts {
}
}
export function getContainingFunctionOrModule(node: Node): Node {
while (true) {
node = node.parent;
if (isFunctionLike(node) || node.kind === SyntaxKind.ModuleDeclaration || node.kind === SyntaxKind.SourceFile) {
return node;
}
}
}
export function getContainingClass(node: Node): ClassLikeDeclaration {
while (true) {
node = node.parent;
+1 -1
View File
@@ -154,7 +154,7 @@ class CompilerBaselineRunner extends RunnerBase {
it (`Correct module resolution tracing for ${fileName}`, () => {
if (options.traceResolution) {
Harness.Baseline.runBaseline("Correct sourcemap content for " + fileName, justName.replace(/\.tsx?$/, ".trace.json"), () => {
Harness.Baseline.runBaseline("Correct module resolution tracing for " + fileName, justName.replace(/\.tsx?$/, ".trace.json"), () => {
return JSON.stringify(result.traceResults || [], undefined, 4);
});
}
+20 -16
View File
@@ -746,7 +746,7 @@ namespace FourSlash {
}
const missingItem = { fileName: fileName, start: start, end: end, isWriteAccess: isWriteAccess };
this.raiseError(`verifyReferencesAtPositionListContains failed - could not find the item: ${JSON.stringify(missingItem, undefined, 2)} in the returned list: (${JSON.stringify(references, undefined, 2)})`);
this.raiseError(`verifyReferencesAtPositionListContains failed - could not find the item: ${stringify(missingItem)} in the returned list: (${stringify(references)})`);
}
public verifyReferencesCountIs(count: number, localFilesOnly = true) {
@@ -800,7 +800,7 @@ namespace FourSlash {
private testDiagnostics(expected: string, diagnostics: ts.Diagnostic[]) {
const realized = ts.realizeDiagnostics(diagnostics, "\r\n");
const actual = JSON.stringify(realized, undefined, 2);
const actual = stringify(realized);
assert.equal(actual, expected);
}
@@ -875,7 +875,7 @@ namespace FourSlash {
}
if (ranges.length !== references.length) {
this.raiseError("Rename location count does not match result.\n\nExpected: " + JSON.stringify(ranges, undefined, 2) + "\n\nActual:" + JSON.stringify(references, undefined, 2));
this.raiseError("Rename location count does not match result.\n\nExpected: " + stringify(ranges) + "\n\nActual:" + stringify(references));
}
ranges = ranges.sort((r1, r2) => r1.start - r2.start);
@@ -888,7 +888,7 @@ namespace FourSlash {
if (reference.textSpan.start !== range.start ||
ts.textSpanEnd(reference.textSpan) !== range.end) {
this.raiseError("Rename location results do not match.\n\nExpected: " + JSON.stringify(ranges, undefined, 2) + "\n\nActual:" + JSON.stringify(references, undefined, 2));
this.raiseError("Rename location results do not match.\n\nExpected: " + stringify(ranges) + "\n\nActual:" + JSON.stringify(references));
}
}
}
@@ -972,7 +972,7 @@ namespace FourSlash {
}
else {
if (actual) {
this.raiseError(`Expected no signature help, but got "${JSON.stringify(actual, undefined, 2)}"`);
this.raiseError(`Expected no signature help, but got "${stringify(actual)}"`);
}
}
}
@@ -1176,7 +1176,7 @@ namespace FourSlash {
public printCurrentParameterHelp() {
const help = this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition);
Harness.IO.log(JSON.stringify(help, undefined, 2));
Harness.IO.log(stringify(help));
}
public printCurrentQuickInfo() {
@@ -1218,7 +1218,7 @@ namespace FourSlash {
public printCurrentSignatureHelp() {
const sigHelp = this.getActiveSignatureHelpItem();
Harness.IO.log(JSON.stringify(sigHelp, undefined, 2));
Harness.IO.log(stringify(sigHelp));
}
public printMemberListMembers() {
@@ -1248,7 +1248,7 @@ namespace FourSlash {
public printReferences() {
const references = this.getReferencesAtCaret();
ts.forEach(references, entry => {
Harness.IO.log(JSON.stringify(entry, undefined, 2));
Harness.IO.log(stringify(entry));
});
}
@@ -1745,8 +1745,8 @@ namespace FourSlash {
function jsonMismatchString() {
return Harness.IO.newLine() +
"expected: '" + Harness.IO.newLine() + JSON.stringify(expected, undefined, 2) + "'" + Harness.IO.newLine() +
"actual: '" + Harness.IO.newLine() + JSON.stringify(actual, undefined, 2) + "'";
"expected: '" + Harness.IO.newLine() + stringify(expected) + "'" + Harness.IO.newLine() +
"actual: '" + Harness.IO.newLine() + stringify(actual) + "'";
}
}
@@ -1961,14 +1961,14 @@ namespace FourSlash {
// if there was an explicit match kind specified, then it should be validated.
if (matchKind !== undefined) {
const missingItem = { name: name, kind: kind, searchValue: searchValue, matchKind: matchKind, fileName: fileName, parentName: parentName };
this.raiseError(`verifyNavigationItemsListContains failed - could not find the item: ${JSON.stringify(missingItem, undefined, 2)} in the returned list: (${JSON.stringify(items, undefined, 2)})`);
this.raiseError(`verifyNavigationItemsListContains failed - could not find the item: ${stringify(missingItem)} in the returned list: (${stringify(items)})`);
}
}
public verifyNavigationBar(json: any) {
const items = this.languageService.getNavigationBarItems(this.activeFile.fileName);
if (JSON.stringify(items, replacer) !== JSON.stringify(json)) {
this.raiseError(`verifyNavigationBar failed - expected: ${JSON.stringify(json, undefined, 2)}, got: ${JSON.stringify(items, replacer, 2)}`);
this.raiseError(`verifyNavigationBar failed - expected: ${stringify(json)}, got: ${stringify(items, replacer)}`);
}
// Make the data easier to read.
@@ -2031,7 +2031,7 @@ namespace FourSlash {
}
const missingItem = { fileName: fileName, start: start, end: end, isWriteAccess: isWriteAccess };
this.raiseError(`verifyOccurrencesAtPositionListContains failed - could not find the item: ${JSON.stringify(missingItem, undefined, 2)} in the returned list: (${JSON.stringify(occurrences, undefined, 2)})`);
this.raiseError(`verifyOccurrencesAtPositionListContains failed - could not find the item: ${stringify(missingItem)} in the returned list: (${stringify(occurrences)})`);
}
public verifyOccurrencesAtPositionListCount(expectedCount: number) {
@@ -2070,7 +2070,7 @@ namespace FourSlash {
}
const missingItem = { fileName: fileName, start: start, end: end, kind: kind };
this.raiseError(`verifyDocumentHighlightsAtPositionListContains failed - could not find the item: ${JSON.stringify(missingItem, undefined, 2)} in the returned list: (${JSON.stringify(documentHighlights, undefined, 2)})`);
this.raiseError(`verifyDocumentHighlightsAtPositionListContains failed - could not find the item: ${stringify(missingItem)} in the returned list: (${stringify(documentHighlights)})`);
}
public verifyDocumentHighlightsAtPositionListCount(expectedCount: number, fileNamesToSearch: string[]) {
@@ -2136,9 +2136,9 @@ namespace FourSlash {
}
}
const itemsString = items.map((item) => JSON.stringify({ name: item.name, kind: item.kind }, undefined, 2)).join(",\n");
const itemsString = items.map(item => stringify({ name: item.name, kind: item.kind })).join(",\n");
this.raiseError(`Expected "${JSON.stringify({ name, text, documentation, kind }, undefined, 2)}" to be in list [${itemsString}]`);
this.raiseError(`Expected "${stringify({ name, text, documentation, kind })}" to be in list [${itemsString}]`);
}
private findFile(indexOrName: any) {
@@ -2701,6 +2701,10 @@ ${code}
}
return result;
}
function stringify(data: any, replacer?: (key: string, value: any) => any): string {
return JSON.stringify(data, replacer, 2);
}
}
namespace FourSlashInterface {
+1 -8
View File
@@ -1770,11 +1770,4 @@ namespace Harness {
}
if (Error) (<any>Error).stackTraceLimit = 1;
}
if (Harness.IO.tryEnableSourceMapsForHost && /^development$/i.test(Harness.IO.getEnvironmentVariable("NODE_ENV"))) {
Harness.IO.tryEnableSourceMapsForHost();
}
// TODO: not sure why Utils.evalFile isn't working with this, eventually will concat it like old compiler instead of eval
eval(Harness.tcServicesFile);
}
+1 -1
View File
@@ -244,7 +244,7 @@ class ProjectRunner extends RunnerBase {
mapRoot: testCase.resolveMapRoot && testCase.mapRoot ? Harness.IO.resolvePath(testCase.mapRoot) : testCase.mapRoot,
sourceRoot: testCase.resolveSourceRoot && testCase.sourceRoot ? Harness.IO.resolvePath(testCase.sourceRoot) : testCase.sourceRoot,
module: moduleKind,
moduleResolution: ts.ModuleResolutionKind.Classic, // currently all tests use classic module resolution kind, this will change in the future
moduleResolution: ts.ModuleResolutionKind.Classic, // currently all tests use classic module resolution kind, this will change in the future
};
// Set the values specified using json
const optionNameMap: ts.Map<ts.CommandLineOption> = {};
+1 -1
View File
@@ -24,7 +24,7 @@ abstract class RunnerBase {
abstract enumerateTestFiles(): string[];
/** Setup the runner's tests so that they are ready to be executed by the harness
/** Setup the runner's tests so that they are ready to be executed by the harness
* The first test should be a describe/it block that sets up the harness's compiler instance appropriately
*/
public abstract initializeTests(): void;
+2 -1
View File
@@ -1,2 +1,3 @@
/// <reference path="lib.es2016.d.ts" />
/// <reference path="lib.es2017.object.d.ts" />
/// <reference path="lib.es2017.object.d.ts" />
/// <reference path="lib.es2017.sharedmemory.d.ts" />
+27
View File
@@ -0,0 +1,27 @@
/// <reference path="lib.es2015.symbol.d.ts" />
/// <reference path="lib.es2015.symbol.wellknown.d.ts" />
interface SharedArrayBuffer {
/**
* Read-only. The length of the ArrayBuffer (in bytes).
*/
readonly byteLength: number;
/*
* The SharedArrayBuffer constructor's length property whose value is 1.
*/
length: number;
/**
* Returns a section of an SharedArrayBuffer.
*/
slice(begin:number, end?:number): SharedArrayBuffer;
readonly [Symbol.species]: SharedArrayBuffer;
readonly [Symbol.toStringTag]: "SharedArrayBuffer";
}
interface SharedArrayBufferConstructor {
readonly prototype: SharedArrayBuffer;
new (byteLength: number): SharedArrayBuffer;
}
declare var SharedArrayBuffer: SharedArrayBufferConstructor;
+13 -1
View File
@@ -136,12 +136,24 @@ interface ObjectConstructor {
*/
getOwnPropertyNames(o: any): string[];
/**
* Creates an object that has null prototype.
* @param o Object to use as a prototype. May be null
*/
create(o: null): any;
/**
* Creates an object that has the specified prototype, and that optionally contains specified properties.
* @param o Object to use as a prototype. May be null
*/
create<T>(o: T): T;
/**
* Creates an object that has the specified prototype, and that optionally contains specified properties.
* @param o Object to use as a prototype. May be null
* @param properties JavaScript object that contains one or more property descriptors.
*/
create(o: any, properties?: PropertyDescriptorMap): any;
create(o: any, properties: PropertyDescriptorMap): any;
/**
* Adds a property to an object, or modifies attributes of an existing property.
+1 -1
View File
@@ -459,7 +459,7 @@ namespace ts.server {
kindModifiers: item.kindModifiers || "",
spans: item.spans.map(span => createTextSpanFromBounds(this.lineOffsetToPosition(fileName, span.start), this.lineOffsetToPosition(fileName, span.end))),
childItems: this.decodeNavigationBarItems(item.childItems, fileName),
indent: 0,
indent: item.indent,
bolded: false,
grayed: false
}));
+1 -1
View File
@@ -268,7 +268,7 @@ namespace ts.server {
}
removeRoot(info: ScriptInfo) {
if (!this.filenameToScript.contains(info.path)) {
if (this.filenameToScript.contains(info.path)) {
this.filenameToScript.remove(info.path);
this.roots = copyListRemovingItem(info, this.roots);
this.resolvedModuleNames.remove(info.path);
+5
View File
@@ -1242,6 +1242,11 @@ declare namespace ts.server.protocol {
* Optional children.
*/
childItems?: NavigationBarItem[];
/**
* Number of levels deep this item should appear.
*/
indent: number;
}
export interface NavBarResponse extends Response {
+7 -2
View File
@@ -872,7 +872,8 @@ namespace ts.server {
start: compilerService.host.positionToLineOffset(fileName, span.start),
end: compilerService.host.positionToLineOffset(fileName, ts.textSpanEnd(span))
})),
childItems: this.decorateNavigationBarItem(project, fileName, item.childItems)
childItems: this.decorateNavigationBarItem(project, fileName, item.childItems),
indent: item.indent
}));
}
@@ -1206,7 +1207,11 @@ namespace ts.server {
// Handle cancellation exceptions
}
this.logError(err, message);
this.output(undefined, request ? request.command : CommandNames.Unknown, request ? request.seq : 0, "Error processing request. " + err.message);
this.output(
undefined,
request ? request.command : CommandNames.Unknown,
request ? request.seq : 0,
"Error processing request. " + (<StackTraceError>err).message + "\n" + (<StackTraceError>err).stack);
}
}
}
+2 -2
View File
@@ -722,5 +722,5 @@ namespace ts.BreakpointResolver {
return spanInNode(node.parent);
}
}
}
}
}
}
+2 -2
View File
@@ -268,9 +268,9 @@ namespace ts.formatting {
return startPos < endPos && current !== SyntaxKind.EndOfFileToken && !isTrivia(current);
}
// when containing node in the tree is token
// when containing node in the tree is token
// but its kind differs from the kind that was returned by the scanner,
// then kind needs to be fixed. This might happen in cases
// then kind needs to be fixed. This might happen in cases
// when parser interprets token differently, i.e keyword treated as identifier
function fixTokenKind(tokenInfo: TokenInfo, container: Node): TokenInfo {
if (isToken(container) && tokenInfo.token.kind !== container.kind) {
+2 -2
View File
@@ -95,9 +95,9 @@ namespace ts.formatting {
//// 4- Context rules with any token combination
//// 5- Non-context rules with specific token combination
//// 6- Non-context rules with any token combination
////
////
//// The member rulesInsertionIndexBitmap is used to describe the number of rules
//// in each sub-bucket (above) hence can be used to know the index of where to insert
//// in each sub-bucket (above) hence can be used to know the index of where to insert
//// the next rule. It's a bitmap which contains 6 different sections each is given 5 bits.
////
//// Example:
+3 -3
View File
@@ -9,7 +9,7 @@ namespace ts.NavigateTo {
// This means "compare in a case insensitive manner."
const baseSensitivity: Intl.CollatorOptions = { sensitivity: "base" };
// Search the declarations in all files and output matched NavigateToItem into array of NavigateToItem[]
// Search the declarations in all files and output matched NavigateToItem into array of NavigateToItem[]
forEach(program.getSourceFiles(), sourceFile => {
cancellationToken.throwIfCancellationRequested();
@@ -17,7 +17,7 @@ namespace ts.NavigateTo {
for (const name in nameToDeclarations) {
const declarations = getProperty(nameToDeclarations, name);
if (declarations) {
// First do a quick check to see if the name of the declaration matches the
// First do a quick check to see if the name of the declaration matches the
// last portion of the (possibly) dotted name they're searching for.
let matches = patternMatcher.getMatchesForLastSegmentOfPattern(name);
@@ -26,7 +26,7 @@ namespace ts.NavigateTo {
}
for (const declaration of declarations) {
// It was a match! If the pattern has dots in it, then also see if the
// It was a match! If the pattern has dots in it, then also see if the
// declaration container matches as well.
if (patternMatcher.patternContainsDots) {
const containers = getContainers(declaration);
+7 -3
View File
@@ -188,7 +188,10 @@ namespace ts.NavigationBar {
case SyntaxKind.ModuleDeclaration:
let moduleDeclaration = <ModuleDeclaration>node;
topLevelNodes.push(node);
addTopLevelNodes((<Block>getInnermostModule(moduleDeclaration).body).statements, topLevelNodes);
const inner = getInnermostModule(moduleDeclaration);
if (inner.body) {
addTopLevelNodes((<Block>inner.body).statements, topLevelNodes);
}
break;
case SyntaxKind.FunctionDeclaration:
@@ -453,7 +456,8 @@ namespace ts.NavigationBar {
function createModuleItem(node: ModuleDeclaration): NavigationBarItem {
const moduleName = getModuleName(node);
const childItems = getItemsWorker(getChildNodes((<Block>getInnermostModule(node).body).statements), createChildItem);
const body = <Block>getInnermostModule(node).body;
const childItems = body ? getItemsWorker(getChildNodes(body.statements), createChildItem) : [];
return getNavigationBarItem(moduleName,
ts.ScriptElementKind.moduleElement,
@@ -611,7 +615,7 @@ namespace ts.NavigationBar {
}
function getInnermostModule(node: ModuleDeclaration): ModuleDeclaration {
while (node.body.kind === SyntaxKind.ModuleDeclaration) {
while (node.body && node.body.kind === SyntaxKind.ModuleDeclaration) {
node = <ModuleDeclaration>node.body;
}
+192 -37
View File
@@ -412,37 +412,56 @@ namespace ts {
const sourceFileOfDeclaration = getSourceFileOfNode(declaration);
// If it is parameter - try and get the jsDoc comment with @param tag from function declaration's jsDoc comments
if (canUseParsedParamTagComments && declaration.kind === SyntaxKind.Parameter) {
ts.forEach(getJsDocCommentTextRange(declaration.parent, sourceFileOfDeclaration), jsDocCommentTextRange => {
const cleanedParamJsDocComment = getCleanedParamJsDocComment(jsDocCommentTextRange.pos, jsDocCommentTextRange.end, sourceFileOfDeclaration);
if (cleanedParamJsDocComment) {
addRange(jsDocCommentParts, cleanedParamJsDocComment);
}
});
if ((declaration.parent.kind === SyntaxKind.FunctionExpression || declaration.parent.kind === SyntaxKind.ArrowFunction) &&
declaration.parent.parent.kind === SyntaxKind.VariableDeclaration) {
addCommentParts(declaration.parent.parent.parent, sourceFileOfDeclaration, getCleanedParamJsDocComment);
}
addCommentParts(declaration.parent, sourceFileOfDeclaration, getCleanedParamJsDocComment);
}
// If this is left side of dotted module declaration, there is no doc comments associated with this node
if (declaration.kind === SyntaxKind.ModuleDeclaration && (<ModuleDeclaration>declaration).body.kind === SyntaxKind.ModuleDeclaration) {
if (declaration.kind === SyntaxKind.ModuleDeclaration && (<ModuleDeclaration>declaration).body && (<ModuleDeclaration>declaration).body.kind === SyntaxKind.ModuleDeclaration) {
return;
}
if ((declaration.kind === SyntaxKind.FunctionExpression || declaration.kind === SyntaxKind.ArrowFunction) &&
declaration.parent.kind === SyntaxKind.VariableDeclaration) {
addCommentParts(declaration.parent.parent, sourceFileOfDeclaration, getCleanedJsDocComment);
}
// If this is dotted module name, get the doc comments from the parent
while (declaration.kind === SyntaxKind.ModuleDeclaration && declaration.parent.kind === SyntaxKind.ModuleDeclaration) {
declaration = <ModuleDeclaration>declaration.parent;
}
addCommentParts(declaration.kind === SyntaxKind.VariableDeclaration ? declaration.parent.parent : declaration,
sourceFileOfDeclaration,
getCleanedJsDocComment);
// Get the cleaned js doc comment text from the declaration
ts.forEach(getJsDocCommentTextRange(
declaration.kind === SyntaxKind.VariableDeclaration ? declaration.parent.parent : declaration, sourceFileOfDeclaration), jsDocCommentTextRange => {
const cleanedJsDocComment = getCleanedJsDocComment(jsDocCommentTextRange.pos, jsDocCommentTextRange.end, sourceFileOfDeclaration);
if (cleanedJsDocComment) {
addRange(jsDocCommentParts, cleanedJsDocComment);
}
});
if (declaration.kind === SyntaxKind.VariableDeclaration) {
const init = (declaration as VariableDeclaration).initializer;
if (init && (init.kind === SyntaxKind.FunctionExpression || init.kind === SyntaxKind.ArrowFunction)) {
// Get the cleaned js doc comment text from the initializer
addCommentParts(init, sourceFileOfDeclaration, getCleanedJsDocComment);
}
}
}
});
return jsDocCommentParts;
function addCommentParts(commented: Node,
sourceFileOfDeclaration: SourceFile,
getCommentPart: (pos: number, end: number, file: SourceFile) => SymbolDisplayPart[]): void {
const ranges = getJsDocCommentTextRange(commented, sourceFileOfDeclaration);
// Get the cleaned js doc comment text from the declaration
ts.forEach(ranges, jsDocCommentTextRange => {
const cleanedComment = getCommentPart(jsDocCommentTextRange.pos, jsDocCommentTextRange.end, sourceFileOfDeclaration);
if (cleanedComment) {
addRange(jsDocCommentParts, cleanedComment);
}
});
}
function getJsDocCommentTextRange(node: Node, sourceFile: SourceFile): TextRange[] {
return ts.map(getJsDocComments(node, sourceFile),
jsDocComment => {
@@ -1051,10 +1070,10 @@ namespace ts {
getCancellationToken?(): HostCancellationToken;
getCurrentDirectory(): string;
getDefaultLibFileName(options: CompilerOptions): string;
log? (s: string): void;
trace? (s: string): void;
error? (s: string): void;
useCaseSensitiveFileNames? (): boolean;
log?(s: string): void;
trace?(s: string): void;
error?(s: string): void;
useCaseSensitiveFileNames?(): boolean;
/*
* LS host can optionally implement this method if it wants to be completely in charge of module name resolution.
@@ -1926,6 +1945,46 @@ namespace ts {
sourceMapText?: string;
}
let commandLineOptionsStringToEnum: CommandLineOptionOfCustomType[];
/**
* Convert an option's string name of enum-value in compiler-options, "options", into its corresponding enum value if possible.
* This is necessary because JS users may pass in string values for enum compiler options (e.g. ModuleKind).
*
* @param options a compiler-options used in transpilation which can contain string value to specify instead of enum values
* @param diagnostics a list of Diagnostic which occur during conversion (e.g invalid option's value)
*/
function ConvertStringForEnumNameInCompilerOptionsToEnumValue(options: CompilerOptions, diagnostics: Diagnostic[]): CompilerOptions {
// Lazily create this value to fix module loading errors.
commandLineOptionsStringToEnum = commandLineOptionsStringToEnum || <CommandLineOptionOfCustomType[]>filter(optionDeclarations, o =>
typeof o.type === "object" && !forEachValue(<Map<number | string>> o.type, v => typeof v !== "number"));
options = clone(options);
for (const opt of commandLineOptionsStringToEnum) {
if (!hasProperty(options, opt.name)) {
continue;
}
const value = options[opt.name];
// Value should be a key of opt.type
if (typeof value === "string") {
// If value is not a string, this will fail
options[opt.name] = parseCustomTypeOption(opt, value, diagnostics);
}
else {
if (!forEachValue(opt.type, v => v === value)) {
// Supplied value isn't a valid enum value.
diagnostics.push(createCompilerDiagnosticForInvalidCustomType(opt));
}
}
}
return options;
}
/*
* This function will compile source text from 'input' argument using specified compiler options.
* If not options are provided - it will use a set of default compiler options.
@@ -1936,7 +1995,9 @@ namespace ts {
* - noResolve = true
*/
export function transpileModule(input: string, transpileOptions: TranspileOptions): TranspileOutput {
const options = transpileOptions.compilerOptions ? clone(transpileOptions.compilerOptions) : getDefaultCompilerOptions();
const diagnostics: Diagnostic[] = [];
const options: CompilerOptions = transpileOptions.compilerOptions ? ConvertStringForEnumNameInCompilerOptionsToEnumValue(transpileOptions.compilerOptions, diagnostics) : getDefaultCompilerOptions();
options.isolatedModules = true;
@@ -1992,13 +2053,16 @@ namespace ts {
directoryExists: directoryExists => true
};
// If there is an error from converting string in compiler-options to its corresponding enum value,
// do not proceed and return and an error;
if (diagnostics.length > 0) {
return { outputText: undefined, diagnostics, sourceMapText };
}
const program = createProgram([inputFileName], options, compilerHost);
let diagnostics: Diagnostic[];
if (transpileOptions.reportDiagnostics) {
diagnostics = [];
addRange(/*to*/ diagnostics, /*from*/ program.getSyntacticDiagnostics(sourceFile));
addRange(/*to*/ diagnostics, /*from*/ program.getOptionsDiagnostics());
}
// Emit
program.emit();
@@ -2486,7 +2550,7 @@ namespace ts {
}
// should be start of dependency list
if (token !== SyntaxKind.OpenBracketToken) {
if (token !== SyntaxKind.OpenBracketToken) {
return true;
}
@@ -2663,7 +2727,7 @@ namespace ts {
return false;
}
/**
/**
* Returns the containing object literal property declaration given a possible name node, e.g. "a" in x = { "a": 1 }
*/
function getContainingObjectLiteralElement(node: Node): ObjectLiteralElement {
@@ -2925,6 +2989,7 @@ namespace ts {
const changesInCompilationSettingsAffectSyntax = oldSettings &&
(oldSettings.target !== newSettings.target ||
oldSettings.module !== newSettings.module ||
oldSettings.moduleResolution !== newSettings.moduleResolution ||
oldSettings.noResolve !== newSettings.noResolve ||
oldSettings.jsx !== newSettings.jsx ||
oldSettings.allowJs !== newSettings.allowJs);
@@ -4018,10 +4083,15 @@ namespace ts {
}
}
function getCompletionsAtPosition(fileName: string, position: number): CompletionInfo {
synchronizeHostData();
const sourceFile = getValidSourceFile(fileName);
if (isInString(sourceFile, position)) {
return getStringLiteralCompletionEntries(sourceFile, position);
}
const completionData = getCompletionData(fileName, position);
if (!completionData) {
return undefined;
@@ -4034,12 +4104,10 @@ namespace ts {
return { isMemberCompletion: false, isNewIdentifierLocation: false, entries: getAllJsDocCompletionEntries() };
}
const sourceFile = getValidSourceFile(fileName);
const entries: CompletionEntry[] = [];
if (isSourceFileJavaScript(sourceFile)) {
const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries);
const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ false);
addRange(entries, getJavaScriptCompletionEntries(sourceFile, location.pos, uniqueNames));
}
else {
@@ -4063,7 +4131,7 @@ namespace ts {
}
}
getCompletionEntriesFromSymbols(symbols, entries);
getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true);
}
// Add keywords if this is not a member completion list
@@ -4113,11 +4181,11 @@ namespace ts {
}));
}
function createCompletionEntry(symbol: Symbol, location: Node): CompletionEntry {
function createCompletionEntry(symbol: Symbol, location: Node, performCharacterChecks: boolean): CompletionEntry {
// Try to get a valid display name for this symbol, if we could not find one, then ignore it.
// We would like to only show things that can be added after a dot, so for instance numeric properties can
// not be accessed with a dot (a.1 <- invalid)
const displayName = getCompletionEntryDisplayNameForSymbol(symbol, program.getCompilerOptions().target, /*performCharacterChecks*/ true, location);
const displayName = getCompletionEntryDisplayNameForSymbol(symbol, program.getCompilerOptions().target, performCharacterChecks, location);
if (!displayName) {
return undefined;
}
@@ -4138,12 +4206,12 @@ namespace ts {
};
}
function getCompletionEntriesFromSymbols(symbols: Symbol[], entries: CompletionEntry[]): Map<string> {
function getCompletionEntriesFromSymbols(symbols: Symbol[], entries: CompletionEntry[], location: Node, performCharacterChecks: boolean): Map<string> {
const start = new Date().getTime();
const uniqueNames: Map<string> = {};
if (symbols) {
for (const symbol of symbols) {
const entry = createCompletionEntry(symbol, location);
const entry = createCompletionEntry(symbol, location, performCharacterChecks);
if (entry) {
const id = escapeIdentifier(entry.name);
if (!lookUp(uniqueNames, id)) {
@@ -4157,6 +4225,93 @@ namespace ts {
log("getCompletionsAtPosition: getCompletionEntriesFromSymbols: " + (new Date().getTime() - start));
return uniqueNames;
}
function getStringLiteralCompletionEntries(sourceFile: SourceFile, position: number) {
const node = findPrecedingToken(position, sourceFile);
if (!node || node.kind !== SyntaxKind.StringLiteral) {
return undefined;
}
const argumentInfo = SignatureHelp.getContainingArgumentInfo(node, position, sourceFile);
if (argumentInfo) {
// Get string literal completions from specialized signatures of the target
return getStringLiteralCompletionEntriesFromCallExpression(argumentInfo);
}
else if (isElementAccessExpression(node.parent) && node.parent.argumentExpression === node) {
// Get all names of properties on the expression
return getStringLiteralCompletionEntriesFromElementAccess(node.parent);
}
else {
// Otherwise, get the completions from the contextual type if one exists
return getStringLiteralCompletionEntriesFromContextualType(<StringLiteral>node);
}
}
function getStringLiteralCompletionEntriesFromCallExpression(argumentInfo: SignatureHelp.ArgumentListInfo) {
const typeChecker = program.getTypeChecker();
const candidates: Signature[] = [];
const entries: CompletionEntry[] = [];
typeChecker.getResolvedSignature(argumentInfo.invocation, candidates);
for (const candidate of candidates) {
if (candidate.parameters.length > argumentInfo.argumentIndex) {
const parameter = candidate.parameters[argumentInfo.argumentIndex];
addStringLiteralCompletionsFromType(typeChecker.getTypeAtLocation(parameter.valueDeclaration), entries);
}
}
if (entries.length) {
return { isMemberCompletion: false, isNewIdentifierLocation: true, entries };
}
return undefined;
}
function getStringLiteralCompletionEntriesFromElementAccess(node: ElementAccessExpression) {
const typeChecker = program.getTypeChecker();
const type = typeChecker.getTypeAtLocation(node.expression);
const entries: CompletionEntry[] = [];
if (type) {
getCompletionEntriesFromSymbols(type.getApparentProperties(), entries, node, /*performCharacterChecks*/false);
if (entries.length) {
return { isMemberCompletion: true, isNewIdentifierLocation: true, entries };
}
}
return undefined;
}
function getStringLiteralCompletionEntriesFromContextualType(node: StringLiteral) {
const typeChecker = program.getTypeChecker();
const type = typeChecker.getContextualType(node);
if (type) {
const entries: CompletionEntry[] = [];
addStringLiteralCompletionsFromType(type, entries);
if (entries.length) {
return { isMemberCompletion: false, isNewIdentifierLocation: false, entries };
}
}
return undefined;
}
function addStringLiteralCompletionsFromType(type: Type, result: CompletionEntry[]): void {
if (!type) {
return;
}
if (type.flags & TypeFlags.Union) {
forEach((<UnionType>type).types, t => addStringLiteralCompletionsFromType(t, result));
}
else {
if (type.flags & TypeFlags.StringLiteral) {
result.push({
name: (<StringLiteralType>type).text,
kindModifiers: ScriptElementKindModifier.none,
kind: ScriptElementKind.variableElement,
sortText: "0"
});
}
}
}
}
function getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails {
@@ -4321,7 +4476,7 @@ namespace ts {
// try get the call/construct signature from the type if it matches
let callExpression: CallExpression;
if (location.kind === SyntaxKind.CallExpression || location.kind === SyntaxKind.NewExpression) {
callExpression = <CallExpression> location;
callExpression = <CallExpression>location;
}
else if (isCallExpressionTarget(location) || isNewExpressionTarget(location)) {
callExpression = <CallExpression>location.parent;
@@ -7610,11 +7765,11 @@ namespace ts {
function isValidBraceCompletionAtPostion(fileName: string, position: number, openingBrace: number): boolean {
// '<' is currently not supported, figuring out if we're in a Generic Type vs. a comparison is too
// '<' is currently not supported, figuring out if we're in a Generic Type vs. a comparison is too
// expensive to do during typing scenarios
// i.e. whether we're dealing with:
// var x = new foo<| ( with class foo<T>{} )
// or
// or
// var y = 3 <|
if (openingBrace === CharacterCodes.lessThan) {
return false;
+2 -2
View File
@@ -295,7 +295,7 @@ namespace ts {
constructor(private shimHost: LanguageServiceShimHost) {
// if shimHost is a COM object then property check will become method call with no arguments.
// 'in' does not have this effect.
// 'in' does not have this effect.
if ("getModuleResolutionsForFile" in this.shimHost) {
this.resolveModuleNames = (moduleNames: string[], containingFile: string) => {
const resolutionsInFile = <Map<string>>JSON.parse(this.shimHost.getModuleResolutionsForFile(containingFile));
@@ -966,7 +966,7 @@ namespace ts {
return this.forwardJSONCall(
"getPreProcessedFileInfo('" + fileName + "')",
() => {
// for now treat files as JavaScript
// for now treat files as JavaScript
const result = preProcessFile(sourceTextSnapshot.getText(0, sourceTextSnapshot.getLength()), /* readImportFiles */ true, /* detectJavaScriptImports */ true);
return {
referencedFiles: this.convertFileReferences(result.referencedFiles),
+383 -380
View File
@@ -3,9 +3,9 @@
namespace ts.SignatureHelp {
// A partially written generic type expression is not guaranteed to have the correct syntax tree. the expression could be parsed as less than/greater than expression or a comma expression
// or some other combination depending on what the user has typed so far. For the purposes of signature help we need to consider any location after "<" as a possible generic type reference.
// To do this, the method will back parse the expression starting at the position required. it will try to parse the current expression as a generic type expression, if it did succeed it
// will return the generic identifier that started the expression (e.g. "foo" in "foo<any, |"). It is then up to the caller to ensure that this is a valid generic expression through
// or some other combination depending on what the user has typed so far. For the purposes of signature help we need to consider any location after "<" as a possible generic type reference.
// To do this, the method will back parse the expression starting at the position required. it will try to parse the current expression as a generic type expression, if it did succeed it
// will return the generic identifier that started the expression (e.g. "foo" in "foo<any, |"). It is then up to the caller to ensure that this is a valid generic expression through
// looking up the type. The method will also keep track of the parameter index inside the expression.
// public static isInPartiallyWrittenTypeArgumentList(syntaxTree: TypeScript.SyntaxTree, position: number): any {
// let token = Syntax.findTokenOnLeft(syntaxTree.sourceUnit(), position, /*includeSkippedTokens*/ true);
@@ -162,15 +162,16 @@ namespace ts.SignatureHelp {
// // Did not find matching token
// return null;
// }
const emptyArray: any[] = [];
const enum ArgumentListKind {
export const enum ArgumentListKind {
TypeArguments,
CallArguments,
TaggedTemplateArguments
}
interface ArgumentListInfo {
export interface ArgumentListInfo {
kind: ArgumentListKind;
invocation: CallLikeExpression;
argumentsSpan: TextSpan;
@@ -188,7 +189,8 @@ namespace ts.SignatureHelp {
return undefined;
}
const argumentInfo = getContainingArgumentInfo(startingToken);
const argumentInfo = getContainingArgumentInfo(startingToken, position, sourceFile);
cancellationToken.throwIfCancellationRequested();
// Semantic filtering of signature help
@@ -202,434 +204,435 @@ namespace ts.SignatureHelp {
cancellationToken.throwIfCancellationRequested();
if (!candidates.length) {
// We didn't have any sig help items produced by the TS compiler. If this is a JS
// 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.
if (isSourceFileJavaScript(sourceFile)) {
return createJavaScriptSignatureHelpItems(argumentInfo);
return createJavaScriptSignatureHelpItems(argumentInfo, program);
}
return undefined;
}
return createSignatureHelpItems(candidates, resolvedSignature, argumentInfo);
return createSignatureHelpItems(candidates, resolvedSignature, argumentInfo, typeChecker);
}
function createJavaScriptSignatureHelpItems(argumentInfo: ArgumentListInfo): SignatureHelpItems {
if (argumentInfo.invocation.kind !== SyntaxKind.CallExpression) {
return undefined;
}
function createJavaScriptSignatureHelpItems(argumentInfo: ArgumentListInfo, program: Program): SignatureHelpItems {
if (argumentInfo.invocation.kind !== SyntaxKind.CallExpression) {
return undefined;
}
// See if we can find some symbol with the call expression name that has call signatures.
const callExpression = <CallExpression>argumentInfo.invocation;
const expression = callExpression.expression;
const name = expression.kind === SyntaxKind.Identifier
? <Identifier> expression
: expression.kind === SyntaxKind.PropertyAccessExpression
? (<PropertyAccessExpression>expression).name
: undefined;
// See if we can find some symbol with the call expression name that has call signatures.
const callExpression = <CallExpression>argumentInfo.invocation;
const expression = callExpression.expression;
const name = expression.kind === SyntaxKind.Identifier
? <Identifier>expression
: expression.kind === SyntaxKind.PropertyAccessExpression
? (<PropertyAccessExpression>expression).name
: undefined;
if (!name || !name.text) {
return undefined;
}
if (!name || !name.text) {
return undefined;
}
const typeChecker = program.getTypeChecker();
for (const sourceFile of program.getSourceFiles()) {
const nameToDeclarations = sourceFile.getNamedDeclarations();
const declarations = getProperty(nameToDeclarations, name.text);
const typeChecker = program.getTypeChecker();
for (const sourceFile of program.getSourceFiles()) {
const nameToDeclarations = sourceFile.getNamedDeclarations();
const declarations = getProperty(nameToDeclarations, name.text);
if (declarations) {
for (const declaration of declarations) {
const symbol = declaration.symbol;
if (symbol) {
const type = typeChecker.getTypeOfSymbolAtLocation(symbol, declaration);
if (type) {
const callSignatures = type.getCallSignatures();
if (callSignatures && callSignatures.length) {
return createSignatureHelpItems(callSignatures, callSignatures[0], argumentInfo);
}
if (declarations) {
for (const declaration of declarations) {
const symbol = declaration.symbol;
if (symbol) {
const type = typeChecker.getTypeOfSymbolAtLocation(symbol, declaration);
if (type) {
const callSignatures = type.getCallSignatures();
if (callSignatures && callSignatures.length) {
return createSignatureHelpItems(callSignatures, callSignatures[0], argumentInfo, typeChecker);
}
}
}
}
}
}
}
/**
* Returns relevant information for the argument list and the current argument if we are
* in the argument of an invocation; returns undefined otherwise.
*/
function getImmediatelyContainingArgumentInfo(node: Node): ArgumentListInfo {
if (node.parent.kind === SyntaxKind.CallExpression || node.parent.kind === SyntaxKind.NewExpression) {
const callExpression = <CallExpression>node.parent;
// There are 3 cases to handle:
// 1. The token introduces a list, and should begin a sig help session
// 2. The token is either not associated with a list, or ends a list, so the session should end
// 3. The token is buried inside a list, and should give sig help
//
// The following are examples of each:
//
// Case 1:
// foo<#T, U>(#a, b) -> The token introduces a list, and should begin a sig help session
// Case 2:
// fo#o<T, U>#(a, b)# -> The token is either not associated with a list, or ends a list, so the session should end
// Case 3:
// foo<T#, U#>(a#, #b#) -> The token is buried inside a list, and should give sig help
// Find out if 'node' is an argument, a type argument, or neither
if (node.kind === SyntaxKind.LessThanToken ||
node.kind === SyntaxKind.OpenParenToken) {
// Find the list that starts right *after* the < or ( token.
// If the user has just opened a list, consider this item 0.
const list = getChildListThatStartsWithOpenerToken(callExpression, node, sourceFile);
const isTypeArgList = callExpression.typeArguments && callExpression.typeArguments.pos === list.pos;
Debug.assert(list !== undefined);
return {
kind: isTypeArgList ? ArgumentListKind.TypeArguments : ArgumentListKind.CallArguments,
invocation: callExpression,
argumentsSpan: getApplicableSpanForArguments(list),
argumentIndex: 0,
argumentCount: getArgumentCount(list)
};
}
// findListItemInfo can return undefined if we are not in parent's argument list
// or type argument list. This includes cases where the cursor is:
// - To the right of the closing paren, non-substitution template, or template tail.
// - Between the type arguments and the arguments (greater than token)
// - On the target of the call (parent.func)
// - On the 'new' keyword in a 'new' expression
const listItemInfo = findListItemInfo(node);
if (listItemInfo) {
const list = listItemInfo.list;
const isTypeArgList = callExpression.typeArguments && callExpression.typeArguments.pos === list.pos;
const argumentIndex = getArgumentIndex(list, node);
const argumentCount = getArgumentCount(list);
Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount,
`argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`);
return {
kind: isTypeArgList ? ArgumentListKind.TypeArguments : ArgumentListKind.CallArguments,
invocation: callExpression,
argumentsSpan: getApplicableSpanForArguments(list),
argumentIndex: argumentIndex,
argumentCount: argumentCount
};
}
}
else if (node.kind === SyntaxKind.NoSubstitutionTemplateLiteral && node.parent.kind === SyntaxKind.TaggedTemplateExpression) {
// Check if we're actually inside the template;
// otherwise we'll fall out and return undefined.
if (isInsideTemplateLiteral(<LiteralExpression>node, position)) {
return getArgumentListInfoForTemplate(<TaggedTemplateExpression>node.parent, /*argumentIndex*/ 0);
}
}
else if (node.kind === SyntaxKind.TemplateHead && node.parent.parent.kind === SyntaxKind.TaggedTemplateExpression) {
const templateExpression = <TemplateExpression>node.parent;
const tagExpression = <TaggedTemplateExpression>templateExpression.parent;
Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression);
const argumentIndex = isInsideTemplateLiteral(<LiteralExpression>node, position) ? 0 : 1;
return getArgumentListInfoForTemplate(tagExpression, argumentIndex);
}
else if (node.parent.kind === SyntaxKind.TemplateSpan && node.parent.parent.parent.kind === SyntaxKind.TaggedTemplateExpression) {
const templateSpan = <TemplateSpan>node.parent;
const templateExpression = <TemplateExpression>templateSpan.parent;
const tagExpression = <TaggedTemplateExpression>templateExpression.parent;
Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression);
// If we're just after a template tail, don't show signature help.
if (node.kind === SyntaxKind.TemplateTail && !isInsideTemplateLiteral(<LiteralExpression>node, position)) {
return undefined;
}
const spanIndex = templateExpression.templateSpans.indexOf(templateSpan);
const argumentIndex = getArgumentIndexForTemplatePiece(spanIndex, node);
return getArgumentListInfoForTemplate(tagExpression, argumentIndex);
}
return undefined;
}
function getArgumentIndex(argumentsList: Node, node: Node) {
// The list we got back can include commas. In the presence of errors it may
// also just have nodes without commas. For example "Foo(a b c)" will have 3
// args without commas. We want to find what index we're at. So we count
// forward until we hit ourselves, only incrementing the index if it isn't a
// comma.
/**
* Returns relevant information for the argument list and the current argument if we are
* in the argument of an invocation; returns undefined otherwise.
*/
function getImmediatelyContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile): ArgumentListInfo {
if (node.parent.kind === SyntaxKind.CallExpression || node.parent.kind === SyntaxKind.NewExpression) {
const callExpression = <CallExpression>node.parent;
// There are 3 cases to handle:
// 1. The token introduces a list, and should begin a sig help session
// 2. The token is either not associated with a list, or ends a list, so the session should end
// 3. The token is buried inside a list, and should give sig help
//
// Note: the subtlety around trailing commas (in getArgumentCount) does not apply
// here. That's because we're only walking forward until we hit the node we're
// on. In that case, even if we're after the trailing comma, we'll still see
// that trailing comma in the list, and we'll have generated the appropriate
// arg index.
let argumentIndex = 0;
const listChildren = argumentsList.getChildren();
for (const child of listChildren) {
if (child === node) {
break;
}
if (child.kind !== SyntaxKind.CommaToken) {
argumentIndex++;
}
// The following are examples of each:
//
// Case 1:
// foo<#T, U>(#a, b) -> The token introduces a list, and should begin a sig help session
// Case 2:
// fo#o<T, U>#(a, b)# -> The token is either not associated with a list, or ends a list, so the session should end
// Case 3:
// foo<T#, U#>(a#, #b#) -> The token is buried inside a list, and should give sig help
// Find out if 'node' is an argument, a type argument, or neither
if (node.kind === SyntaxKind.LessThanToken ||
node.kind === SyntaxKind.OpenParenToken) {
// Find the list that starts right *after* the < or ( token.
// If the user has just opened a list, consider this item 0.
const list = getChildListThatStartsWithOpenerToken(callExpression, node, sourceFile);
const isTypeArgList = callExpression.typeArguments && callExpression.typeArguments.pos === list.pos;
Debug.assert(list !== undefined);
return {
kind: isTypeArgList ? ArgumentListKind.TypeArguments : ArgumentListKind.CallArguments,
invocation: callExpression,
argumentsSpan: getApplicableSpanForArguments(list, sourceFile),
argumentIndex: 0,
argumentCount: getArgumentCount(list)
};
}
return argumentIndex;
}
// findListItemInfo can return undefined if we are not in parent's argument list
// or type argument list. This includes cases where the cursor is:
// - To the right of the closing paren, non-substitution template, or template tail.
// - Between the type arguments and the arguments (greater than token)
// - On the target of the call (parent.func)
// - On the 'new' keyword in a 'new' expression
const listItemInfo = findListItemInfo(node);
if (listItemInfo) {
const list = listItemInfo.list;
const isTypeArgList = callExpression.typeArguments && callExpression.typeArguments.pos === list.pos;
function getArgumentCount(argumentsList: Node) {
// The argument count for a list is normally the number of non-comma children it has.
// For example, if you have "Foo(a,b)" then there will be three children of the arg
// list 'a' '<comma>' 'b'. So, in this case the arg count will be 2. However, there
// is a small subtlety. If you have "Foo(a,)", then the child list will just have
// 'a' '<comma>'. So, in the case where the last child is a comma, we increase the
// arg count by one to compensate.
//
// Note: this subtlety only applies to the last comma. If you had "Foo(a,," then
// we'll have: 'a' '<comma>' '<missing>'
// That will give us 2 non-commas. We then add one for the last comma, givin us an
// arg count of 3.
const listChildren = argumentsList.getChildren();
const argumentIndex = getArgumentIndex(list, node);
const argumentCount = getArgumentCount(list);
let argumentCount = countWhere(listChildren, arg => arg.kind !== SyntaxKind.CommaToken);
if (listChildren.length > 0 && lastOrUndefined(listChildren).kind === SyntaxKind.CommaToken) {
argumentCount++;
}
Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount,
`argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`);
return argumentCount;
}
// spanIndex is either the index for a given template span.
// This does not give appropriate results for a NoSubstitutionTemplateLiteral
function getArgumentIndexForTemplatePiece(spanIndex: number, node: Node): number {
// Because the TemplateStringsArray is the first argument, we have to offset each substitution expression by 1.
// There are three cases we can encounter:
// 1. We are precisely in the template literal (argIndex = 0).
// 2. We are in or to the right of the substitution expression (argIndex = spanIndex + 1).
// 3. We are directly to the right of the template literal, but because we look for the token on the left,
// not enough to put us in the substitution expression; we should consider ourselves part of
// the *next* span's expression by offsetting the index (argIndex = (spanIndex + 1) + 1).
//
// Example: f `# abcd $#{# 1 + 1# }# efghi ${ #"#hello"# } # `
// ^ ^ ^ ^ ^ ^ ^ ^ ^
// Case: 1 1 3 2 1 3 2 2 1
Debug.assert(position >= node.getStart(), "Assumed 'position' could not occur before node.");
if (isTemplateLiteralKind(node.kind)) {
if (isInsideTemplateLiteral(<LiteralExpression>node, position)) {
return 0;
}
return spanIndex + 2;
}
return spanIndex + 1;
}
function getArgumentListInfoForTemplate(tagExpression: TaggedTemplateExpression, argumentIndex: number): ArgumentListInfo {
// argumentCount is either 1 or (numSpans + 1) to account for the template strings array argument.
const argumentCount = tagExpression.template.kind === SyntaxKind.NoSubstitutionTemplateLiteral
? 1
: (<TemplateExpression>tagExpression.template).templateSpans.length + 1;
Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount, `argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`);
return {
kind: ArgumentListKind.TaggedTemplateArguments,
invocation: tagExpression,
argumentsSpan: getApplicableSpanForTaggedTemplate(tagExpression),
argumentIndex: argumentIndex,
argumentCount: argumentCount
};
}
function getApplicableSpanForArguments(argumentsList: Node): TextSpan {
// We use full start and skip trivia on the end because we want to include trivia on
// both sides. For example,
//
// foo( /*comment */ a, b, c /*comment*/ )
// | |
//
// The applicable span is from the first bar to the second bar (inclusive,
// but not including parentheses)
const applicableSpanStart = argumentsList.getFullStart();
const applicableSpanEnd = skipTrivia(sourceFile.text, argumentsList.getEnd(), /*stopAfterLineBreak*/ false);
return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart);
}
function getApplicableSpanForTaggedTemplate(taggedTemplate: TaggedTemplateExpression): TextSpan {
const template = taggedTemplate.template;
const applicableSpanStart = template.getStart();
let applicableSpanEnd = template.getEnd();
// We need to adjust the end position for the case where the template does not have a tail.
// Otherwise, we will not show signature help past the expression.
// For example,
//
// ` ${ 1 + 1 foo(10)
// | |
//
// This is because a Missing node has no width. However, what we actually want is to include trivia
// leading up to the next token in case the user is about to type in a TemplateMiddle or TemplateTail.
if (template.kind === SyntaxKind.TemplateExpression) {
const lastSpan = lastOrUndefined((<TemplateExpression>template).templateSpans);
if (lastSpan.literal.getFullWidth() === 0) {
applicableSpanEnd = skipTrivia(sourceFile.text, applicableSpanEnd, /*stopAfterLineBreak*/ false);
}
}
return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart);
}
function getContainingArgumentInfo(node: Node): ArgumentListInfo {
for (let n = node; n.kind !== SyntaxKind.SourceFile; n = n.parent) {
if (isFunctionBlock(n)) {
return undefined;
}
// 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.
if (n.pos < n.parent.pos || n.end > n.parent.end) {
Debug.fail("Node of kind " + n.kind + " is not a subspan of its parent of kind " + n.parent.kind);
}
const argumentInfo = getImmediatelyContainingArgumentInfo(n);
if (argumentInfo) {
return argumentInfo;
}
// TODO: Handle generic call with incomplete syntax
return {
kind: isTypeArgList ? ArgumentListKind.TypeArguments : ArgumentListKind.CallArguments,
invocation: callExpression,
argumentsSpan: getApplicableSpanForArguments(list, sourceFile),
argumentIndex: argumentIndex,
argumentCount: argumentCount
};
}
return undefined;
}
function getChildListThatStartsWithOpenerToken(parent: Node, openerToken: Node, sourceFile: SourceFile): Node {
const children = parent.getChildren(sourceFile);
const indexOfOpenerToken = children.indexOf(openerToken);
Debug.assert(indexOfOpenerToken >= 0 && children.length > indexOfOpenerToken + 1);
return children[indexOfOpenerToken + 1];
else if (node.kind === SyntaxKind.NoSubstitutionTemplateLiteral && node.parent.kind === SyntaxKind.TaggedTemplateExpression) {
// Check if we're actually inside the template;
// otherwise we'll fall out and return undefined.
if (isInsideTemplateLiteral(<LiteralExpression>node, position)) {
return getArgumentListInfoForTemplate(<TaggedTemplateExpression>node.parent, /*argumentIndex*/ 0, sourceFile);
}
}
else if (node.kind === SyntaxKind.TemplateHead && node.parent.parent.kind === SyntaxKind.TaggedTemplateExpression) {
const templateExpression = <TemplateExpression>node.parent;
const tagExpression = <TaggedTemplateExpression>templateExpression.parent;
Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression);
/**
* The selectedItemIndex could be negative for several reasons.
* 1. There are too many arguments for all of the overloads
* 2. None of the overloads were type compatible
* The solution here is to try to pick the best overload by picking
* either the first one that has an appropriate number of parameters,
* or the one with the most parameters.
*/
function selectBestInvalidOverloadIndex(candidates: Signature[], argumentCount: number): number {
let maxParamsSignatureIndex = -1;
let maxParams = -1;
for (let i = 0; i < candidates.length; i++) {
const candidate = candidates[i];
const argumentIndex = isInsideTemplateLiteral(<LiteralExpression>node, position) ? 0 : 1;
if (candidate.hasRestParameter || candidate.parameters.length >= argumentCount) {
return i;
}
return getArgumentListInfoForTemplate(tagExpression, argumentIndex, sourceFile);
}
else if (node.parent.kind === SyntaxKind.TemplateSpan && node.parent.parent.parent.kind === SyntaxKind.TaggedTemplateExpression) {
const templateSpan = <TemplateSpan>node.parent;
const templateExpression = <TemplateExpression>templateSpan.parent;
const tagExpression = <TaggedTemplateExpression>templateExpression.parent;
Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression);
if (candidate.parameters.length > maxParams) {
maxParams = candidate.parameters.length;
maxParamsSignatureIndex = i;
}
// If we're just after a template tail, don't show signature help.
if (node.kind === SyntaxKind.TemplateTail && !isInsideTemplateLiteral(<LiteralExpression>node, position)) {
return undefined;
}
return maxParamsSignatureIndex;
const spanIndex = templateExpression.templateSpans.indexOf(templateSpan);
const argumentIndex = getArgumentIndexForTemplatePiece(spanIndex, node, position);
return getArgumentListInfoForTemplate(tagExpression, argumentIndex, sourceFile);
}
function createSignatureHelpItems(candidates: Signature[], bestSignature: Signature, argumentListInfo: ArgumentListInfo): SignatureHelpItems {
const applicableSpan = argumentListInfo.argumentsSpan;
const isTypeParameterList = argumentListInfo.kind === ArgumentListKind.TypeArguments;
return undefined;
}
const invocation = argumentListInfo.invocation;
const callTarget = getInvokedExpression(invocation);
const callTargetSymbol = typeChecker.getSymbolAtLocation(callTarget);
const callTargetDisplayParts = callTargetSymbol && symbolToDisplayParts(typeChecker, callTargetSymbol, /*enclosingDeclaration*/ undefined, /*meaning*/ undefined);
const items: SignatureHelpItem[] = map(candidates, candidateSignature => {
let signatureHelpParameters: SignatureHelpParameter[];
const prefixDisplayParts: SymbolDisplayPart[] = [];
const suffixDisplayParts: SymbolDisplayPart[] = [];
function getArgumentIndex(argumentsList: Node, node: Node) {
// The list we got back can include commas. In the presence of errors it may
// also just have nodes without commas. For example "Foo(a b c)" will have 3
// args without commas. We want to find what index we're at. So we count
// forward until we hit ourselves, only incrementing the index if it isn't a
// comma.
//
// Note: the subtlety around trailing commas (in getArgumentCount) does not apply
// here. That's because we're only walking forward until we hit the node we're
// on. In that case, even if we're after the trailing comma, we'll still see
// that trailing comma in the list, and we'll have generated the appropriate
// arg index.
let argumentIndex = 0;
const listChildren = argumentsList.getChildren();
for (const child of listChildren) {
if (child === node) {
break;
}
if (child.kind !== SyntaxKind.CommaToken) {
argumentIndex++;
}
}
if (callTargetDisplayParts) {
addRange(prefixDisplayParts, callTargetDisplayParts);
}
return argumentIndex;
}
if (isTypeParameterList) {
prefixDisplayParts.push(punctuationPart(SyntaxKind.LessThanToken));
const typeParameters = candidateSignature.typeParameters;
signatureHelpParameters = typeParameters && typeParameters.length > 0 ? map(typeParameters, createSignatureHelpParameterForTypeParameter) : emptyArray;
suffixDisplayParts.push(punctuationPart(SyntaxKind.GreaterThanToken));
const parameterParts = mapToDisplayParts(writer =>
typeChecker.getSymbolDisplayBuilder().buildDisplayForParametersAndDelimiters(candidateSignature.thisType, candidateSignature.parameters, writer, invocation));
addRange(suffixDisplayParts, parameterParts);
}
else {
const typeParameterParts = mapToDisplayParts(writer =>
typeChecker.getSymbolDisplayBuilder().buildDisplayForTypeParametersAndDelimiters(candidateSignature.typeParameters, writer, invocation));
addRange(prefixDisplayParts, typeParameterParts);
prefixDisplayParts.push(punctuationPart(SyntaxKind.OpenParenToken));
function getArgumentCount(argumentsList: Node) {
// The argument count for a list is normally the number of non-comma children it has.
// For example, if you have "Foo(a,b)" then there will be three children of the arg
// list 'a' '<comma>' 'b'. So, in this case the arg count will be 2. However, there
// is a small subtlety. If you have "Foo(a,)", then the child list will just have
// 'a' '<comma>'. So, in the case where the last child is a comma, we increase the
// arg count by one to compensate.
//
// Note: this subtlety only applies to the last comma. If you had "Foo(a,," then
// we'll have: 'a' '<comma>' '<missing>'
// That will give us 2 non-commas. We then add one for the last comma, givin us an
// arg count of 3.
const listChildren = argumentsList.getChildren();
const parameters = candidateSignature.parameters;
signatureHelpParameters = parameters.length > 0 ? map(parameters, createSignatureHelpParameterForParameter) : emptyArray;
suffixDisplayParts.push(punctuationPart(SyntaxKind.CloseParenToken));
}
let argumentCount = countWhere(listChildren, arg => arg.kind !== SyntaxKind.CommaToken);
if (listChildren.length > 0 && lastOrUndefined(listChildren).kind === SyntaxKind.CommaToken) {
argumentCount++;
}
const returnTypeParts = mapToDisplayParts(writer =>
typeChecker.getSymbolDisplayBuilder().buildReturnTypeDisplay(candidateSignature, writer, invocation));
addRange(suffixDisplayParts, returnTypeParts);
return argumentCount;
}
return {
isVariadic: candidateSignature.hasRestParameter,
prefixDisplayParts,
suffixDisplayParts,
separatorDisplayParts: [punctuationPart(SyntaxKind.CommaToken), spacePart()],
parameters: signatureHelpParameters,
documentation: candidateSignature.getDocumentationComment()
};
});
// spanIndex is either the index for a given template span.
// This does not give appropriate results for a NoSubstitutionTemplateLiteral
function getArgumentIndexForTemplatePiece(spanIndex: number, node: Node, position: number): number {
// Because the TemplateStringsArray is the first argument, we have to offset each substitution expression by 1.
// There are three cases we can encounter:
// 1. We are precisely in the template literal (argIndex = 0).
// 2. We are in or to the right of the substitution expression (argIndex = spanIndex + 1).
// 3. We are directly to the right of the template literal, but because we look for the token on the left,
// not enough to put us in the substitution expression; we should consider ourselves part of
// the *next* span's expression by offsetting the index (argIndex = (spanIndex + 1) + 1).
//
// Example: f `# abcd $#{# 1 + 1# }# efghi ${ #"#hello"# } # `
// ^ ^ ^ ^ ^ ^ ^ ^ ^
// Case: 1 1 3 2 1 3 2 2 1
Debug.assert(position >= node.getStart(), "Assumed 'position' could not occur before node.");
if (isTemplateLiteralKind(node.kind)) {
if (isInsideTemplateLiteral(<LiteralExpression>node, position)) {
return 0;
}
return spanIndex + 2;
}
return spanIndex + 1;
}
const argumentIndex = argumentListInfo.argumentIndex;
function getArgumentListInfoForTemplate(tagExpression: TaggedTemplateExpression, argumentIndex: number, sourceFile: SourceFile): ArgumentListInfo {
// argumentCount is either 1 or (numSpans + 1) to account for the template strings array argument.
const argumentCount = tagExpression.template.kind === SyntaxKind.NoSubstitutionTemplateLiteral
? 1
: (<TemplateExpression>tagExpression.template).templateSpans.length + 1;
// argumentCount is the *apparent* number of arguments.
const argumentCount = argumentListInfo.argumentCount;
Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount, `argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`);
let selectedItemIndex = candidates.indexOf(bestSignature);
if (selectedItemIndex < 0) {
selectedItemIndex = selectBestInvalidOverloadIndex(candidates, argumentCount);
return {
kind: ArgumentListKind.TaggedTemplateArguments,
invocation: tagExpression,
argumentsSpan: getApplicableSpanForTaggedTemplate(tagExpression, sourceFile),
argumentIndex: argumentIndex,
argumentCount: argumentCount
};
}
function getApplicableSpanForArguments(argumentsList: Node, sourceFile: SourceFile): TextSpan {
// We use full start and skip trivia on the end because we want to include trivia on
// both sides. For example,
//
// foo( /*comment */ a, b, c /*comment*/ )
// | |
//
// The applicable span is from the first bar to the second bar (inclusive,
// but not including parentheses)
const applicableSpanStart = argumentsList.getFullStart();
const applicableSpanEnd = skipTrivia(sourceFile.text, argumentsList.getEnd(), /*stopAfterLineBreak*/ false);
return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart);
}
function getApplicableSpanForTaggedTemplate(taggedTemplate: TaggedTemplateExpression, sourceFile: SourceFile): TextSpan {
const template = taggedTemplate.template;
const applicableSpanStart = template.getStart();
let applicableSpanEnd = template.getEnd();
// We need to adjust the end position for the case where the template does not have a tail.
// Otherwise, we will not show signature help past the expression.
// For example,
//
// ` ${ 1 + 1 foo(10)
// | |
//
// This is because a Missing node has no width. However, what we actually want is to include trivia
// leading up to the next token in case the user is about to type in a TemplateMiddle or TemplateTail.
if (template.kind === SyntaxKind.TemplateExpression) {
const lastSpan = lastOrUndefined((<TemplateExpression>template).templateSpans);
if (lastSpan.literal.getFullWidth() === 0) {
applicableSpanEnd = skipTrivia(sourceFile.text, applicableSpanEnd, /*stopAfterLineBreak*/ false);
}
}
return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart);
}
export function getContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile): ArgumentListInfo {
for (let n = node; n.kind !== SyntaxKind.SourceFile; n = n.parent) {
if (isFunctionBlock(n)) {
return undefined;
}
Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount, `argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`);
// 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.
if (n.pos < n.parent.pos || n.end > n.parent.end) {
Debug.fail("Node of kind " + n.kind + " is not a subspan of its parent of kind " + n.parent.kind);
}
const argumentInfo = getImmediatelyContainingArgumentInfo(n, position, sourceFile);
if (argumentInfo) {
return argumentInfo;
}
// TODO: Handle generic call with incomplete syntax
}
return undefined;
}
function getChildListThatStartsWithOpenerToken(parent: Node, openerToken: Node, sourceFile: SourceFile): Node {
const children = parent.getChildren(sourceFile);
const indexOfOpenerToken = children.indexOf(openerToken);
Debug.assert(indexOfOpenerToken >= 0 && children.length > indexOfOpenerToken + 1);
return children[indexOfOpenerToken + 1];
}
/**
* The selectedItemIndex could be negative for several reasons.
* 1. There are too many arguments for all of the overloads
* 2. None of the overloads were type compatible
* The solution here is to try to pick the best overload by picking
* either the first one that has an appropriate number of parameters,
* or the one with the most parameters.
*/
function selectBestInvalidOverloadIndex(candidates: Signature[], argumentCount: number): number {
let maxParamsSignatureIndex = -1;
let maxParams = -1;
for (let i = 0; i < candidates.length; i++) {
const candidate = candidates[i];
if (candidate.hasRestParameter || candidate.parameters.length >= argumentCount) {
return i;
}
if (candidate.parameters.length > maxParams) {
maxParams = candidate.parameters.length;
maxParamsSignatureIndex = i;
}
}
return maxParamsSignatureIndex;
}
function createSignatureHelpItems(candidates: Signature[], bestSignature: Signature, argumentListInfo: ArgumentListInfo, typeChecker: TypeChecker): SignatureHelpItems {
const applicableSpan = argumentListInfo.argumentsSpan;
const isTypeParameterList = argumentListInfo.kind === ArgumentListKind.TypeArguments;
const invocation = argumentListInfo.invocation;
const callTarget = getInvokedExpression(invocation);
const callTargetSymbol = typeChecker.getSymbolAtLocation(callTarget);
const callTargetDisplayParts = callTargetSymbol && symbolToDisplayParts(typeChecker, callTargetSymbol, /*enclosingDeclaration*/ undefined, /*meaning*/ undefined);
const items: SignatureHelpItem[] = map(candidates, candidateSignature => {
let signatureHelpParameters: SignatureHelpParameter[];
const prefixDisplayParts: SymbolDisplayPart[] = [];
const suffixDisplayParts: SymbolDisplayPart[] = [];
if (callTargetDisplayParts) {
addRange(prefixDisplayParts, callTargetDisplayParts);
}
if (isTypeParameterList) {
prefixDisplayParts.push(punctuationPart(SyntaxKind.LessThanToken));
const typeParameters = candidateSignature.typeParameters;
signatureHelpParameters = typeParameters && typeParameters.length > 0 ? map(typeParameters, createSignatureHelpParameterForTypeParameter) : emptyArray;
suffixDisplayParts.push(punctuationPart(SyntaxKind.GreaterThanToken));
const parameterParts = mapToDisplayParts(writer =>
typeChecker.getSymbolDisplayBuilder().buildDisplayForParametersAndDelimiters(candidateSignature.thisType, candidateSignature.parameters, writer, invocation));
addRange(suffixDisplayParts, parameterParts);
}
else {
const typeParameterParts = mapToDisplayParts(writer =>
typeChecker.getSymbolDisplayBuilder().buildDisplayForTypeParametersAndDelimiters(candidateSignature.typeParameters, writer, invocation));
addRange(prefixDisplayParts, typeParameterParts);
prefixDisplayParts.push(punctuationPart(SyntaxKind.OpenParenToken));
const parameters = candidateSignature.parameters;
signatureHelpParameters = parameters.length > 0 ? map(parameters, createSignatureHelpParameterForParameter) : emptyArray;
suffixDisplayParts.push(punctuationPart(SyntaxKind.CloseParenToken));
}
const returnTypeParts = mapToDisplayParts(writer =>
typeChecker.getSymbolDisplayBuilder().buildReturnTypeDisplay(candidateSignature, writer, invocation));
addRange(suffixDisplayParts, returnTypeParts);
return {
items,
applicableSpan,
selectedItemIndex,
argumentIndex,
argumentCount
isVariadic: candidateSignature.hasRestParameter,
prefixDisplayParts,
suffixDisplayParts,
separatorDisplayParts: [punctuationPart(SyntaxKind.CommaToken), spacePart()],
parameters: signatureHelpParameters,
documentation: candidateSignature.getDocumentationComment()
};
});
function createSignatureHelpParameterForParameter(parameter: Symbol): SignatureHelpParameter {
const displayParts = mapToDisplayParts(writer =>
typeChecker.getSymbolDisplayBuilder().buildParameterDisplay(parameter, writer, invocation));
const argumentIndex = argumentListInfo.argumentIndex;
return {
name: parameter.name,
documentation: parameter.getDocumentationComment(),
displayParts,
isOptional: typeChecker.isOptionalParameter(<ParameterDeclaration>parameter.valueDeclaration)
};
}
// argumentCount is the *apparent* number of arguments.
const argumentCount = argumentListInfo.argumentCount;
function createSignatureHelpParameterForTypeParameter(typeParameter: TypeParameter): SignatureHelpParameter {
const displayParts = mapToDisplayParts(writer =>
typeChecker.getSymbolDisplayBuilder().buildTypeParameterDisplay(typeParameter, writer, invocation));
let selectedItemIndex = candidates.indexOf(bestSignature);
if (selectedItemIndex < 0) {
selectedItemIndex = selectBestInvalidOverloadIndex(candidates, argumentCount);
}
return {
name: typeParameter.symbol.name,
documentation: emptyArray,
displayParts,
isOptional: false
};
}
Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount, `argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`);
return {
items,
applicableSpan,
selectedItemIndex,
argumentIndex,
argumentCount
};
function createSignatureHelpParameterForParameter(parameter: Symbol): SignatureHelpParameter {
const displayParts = mapToDisplayParts(writer =>
typeChecker.getSymbolDisplayBuilder().buildParameterDisplay(parameter, writer, invocation));
return {
name: parameter.name,
documentation: parameter.getDocumentationComment(),
displayParts,
isOptional: typeChecker.isOptionalParameter(<ParameterDeclaration>parameter.valueDeclaration)
};
}
function createSignatureHelpParameterForTypeParameter(typeParameter: TypeParameter): SignatureHelpParameter {
const displayParts = mapToDisplayParts(writer =>
typeChecker.getSymbolDisplayBuilder().buildTypeParameterDisplay(typeParameter, writer, invocation));
return {
name: typeParameter.symbol.name,
documentation: emptyArray,
displayParts,
isOptional: false
};
}
}
}
+21 -3
View File
@@ -426,9 +426,27 @@ namespace ts {
}
}
export function isInString(sourceFile: SourceFile, position: number) {
const token = getTokenAtPosition(sourceFile, position);
return token && (token.kind === SyntaxKind.StringLiteral || token.kind === SyntaxKind.StringLiteralType) && position > token.getStart(sourceFile);
export function isInString(sourceFile: SourceFile, position: number): boolean {
const previousToken = findPrecedingToken(position, sourceFile);
if (previousToken &&
(previousToken.kind === SyntaxKind.StringLiteral || previousToken.kind === SyntaxKind.StringLiteralType)) {
const start = previousToken.getStart();
const end = previousToken.getEnd();
// To be "in" one of these literals, the position has to be:
// 1. entirely within the token text.
// 2. at the end position of an unterminated token.
// 3. at the end of a regular expression (due to trailing flags like '/foo/g').
if (start < position && position < end) {
return true;
}
if (position === end) {
return !!(<LiteralExpression>previousToken).isUnterminated;
}
}
return false;
}
export function isInComment(sourceFile: SourceFile, position: number) {