From e223b2e53cf0c7afab71e15e556fe4b9c8ca4011 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Wed, 6 Jan 2016 12:47:26 -0800 Subject: [PATCH 01/14] Clean up unrelated changes --- src/compiler/binder.ts | 31 +- src/compiler/checker.ts | 382 +++++++++++++++--- src/compiler/parser.ts | 290 +++++++------ src/compiler/scanner.ts | 97 +++++ src/compiler/sys.ts | 2 - src/compiler/types.ts | 8 +- src/compiler/utilities.ts | 69 +++- src/harness/fourslash.ts | 25 ++ src/harness/harness.ts | 2 +- src/server/server.ts | 46 +++ src/server/session.ts | 4 +- src/services/services.ts | 2 +- .../jsFileCompilationSyntaxError.errors.txt | 5 +- tests/cases/fourslash/completionInJsDoc.ts | 8 +- .../fourslash/getJavaScriptCompletions1.ts | 10 + .../fourslash/getJavaScriptCompletions10.ts | 11 + .../fourslash/getJavaScriptCompletions11.ts | 11 + .../fourslash/getJavaScriptCompletions12.ts | 36 ++ .../fourslash/getJavaScriptCompletions13.ts | 26 ++ .../fourslash/getJavaScriptCompletions14.ts | 13 + .../fourslash/getJavaScriptCompletions15.ts | 29 ++ .../fourslash/getJavaScriptCompletions2.ts | 10 + .../fourslash/getJavaScriptCompletions3.ts | 10 + .../fourslash/getJavaScriptCompletions4.ts | 10 + .../fourslash/getJavaScriptCompletions5.ts | 15 + .../fourslash/getJavaScriptCompletions6.ts | 13 + .../fourslash/getJavaScriptCompletions7.ts | 13 + .../fourslash/getJavaScriptCompletions8.ts | 12 + .../fourslash/getJavaScriptCompletions9.ts | 12 + .../fourslash/getJavaScriptQuickInfo1.ts | 9 + .../fourslash/getJavaScriptQuickInfo2.ts | 9 + .../fourslash/getJavaScriptQuickInfo3.ts | 9 + .../fourslash/getJavaScriptQuickInfo4.ts | 9 + .../fourslash/getJavaScriptQuickInfo5.ts | 9 + .../fourslash/getJavaScriptQuickInfo6.ts | 9 + .../fourslash/getJavaScriptQuickInfo7.ts | 10 + .../fourslash/javaScriptModulesError1.ts | 12 + tests/cases/unittests/jsDocParsing.ts | 35 +- 38 files changed, 1056 insertions(+), 257 deletions(-) create mode 100644 tests/cases/fourslash/getJavaScriptCompletions1.ts create mode 100644 tests/cases/fourslash/getJavaScriptCompletions10.ts create mode 100644 tests/cases/fourslash/getJavaScriptCompletions11.ts create mode 100644 tests/cases/fourslash/getJavaScriptCompletions12.ts create mode 100644 tests/cases/fourslash/getJavaScriptCompletions13.ts create mode 100644 tests/cases/fourslash/getJavaScriptCompletions14.ts create mode 100644 tests/cases/fourslash/getJavaScriptCompletions15.ts create mode 100644 tests/cases/fourslash/getJavaScriptCompletions2.ts create mode 100644 tests/cases/fourslash/getJavaScriptCompletions3.ts create mode 100644 tests/cases/fourslash/getJavaScriptCompletions4.ts create mode 100644 tests/cases/fourslash/getJavaScriptCompletions5.ts create mode 100644 tests/cases/fourslash/getJavaScriptCompletions6.ts create mode 100644 tests/cases/fourslash/getJavaScriptCompletions7.ts create mode 100644 tests/cases/fourslash/getJavaScriptCompletions8.ts create mode 100644 tests/cases/fourslash/getJavaScriptCompletions9.ts create mode 100644 tests/cases/fourslash/getJavaScriptQuickInfo1.ts create mode 100644 tests/cases/fourslash/getJavaScriptQuickInfo2.ts create mode 100644 tests/cases/fourslash/getJavaScriptQuickInfo3.ts create mode 100644 tests/cases/fourslash/getJavaScriptQuickInfo4.ts create mode 100644 tests/cases/fourslash/getJavaScriptQuickInfo5.ts create mode 100644 tests/cases/fourslash/getJavaScriptQuickInfo6.ts create mode 100644 tests/cases/fourslash/getJavaScriptQuickInfo7.ts create mode 100644 tests/cases/fourslash/javaScriptModulesError1.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index f7108c5d2d714..6b63cbdb5dfac 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -240,6 +240,15 @@ namespace ts { case SyntaxKind.FunctionDeclaration: case SyntaxKind.ClassDeclaration: return node.flags & NodeFlags.Default ? "default" : undefined; + case SyntaxKind.JSDocFunctionType: + return isJSDocConstructSignature(node) ? "__new" : "__call"; + case SyntaxKind.Parameter: + // Parameters with names are handled at the top of this function. Parameters + // without names can only come from JSDocFunctionTypes. + Debug.assert(node.parent.kind === SyntaxKind.JSDocFunctionType); + let functionType = node.parent; + let index = indexOf(functionType.parameters, node); + return "p" + index; } } @@ -405,7 +414,6 @@ namespace ts { addToContainerChain(container); } - else if (containerFlags & ContainerFlags.IsBlockScopedContainer) { blockScopeContainer = node; blockScopeContainer.locals = undefined; @@ -440,6 +448,10 @@ namespace ts { labelStack = labelIndexMap = implicitLabels = undefined; } + if (isInJavaScriptFile(node) && node.jsDocComment) { + bind(node.jsDocComment); + } + bindReachableStatement(node); if (currentReachabilityState === Reachability.Reachable && isFunctionLikeKind(kind) && nodeIsPresent((node).body)) { @@ -688,8 +700,9 @@ namespace ts { case SyntaxKind.ClassDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.EnumDeclaration: - case SyntaxKind.TypeLiteral: case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.TypeLiteral: + case SyntaxKind.JSDocRecordType: return ContainerFlags.IsContainer; case SyntaxKind.CallSignature: @@ -775,6 +788,7 @@ namespace ts { case SyntaxKind.TypeLiteral: case SyntaxKind.ObjectLiteralExpression: case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.JSDocRecordType: // Interface/Object-types always have their children added to the 'members' of // their container. They are only accessible through an instance of their // container, and are never in scope otherwise (even inside the body of the @@ -795,6 +809,7 @@ namespace ts { case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: + case SyntaxKind.JSDocFunctionType: case SyntaxKind.TypeAliasDeclaration: // All the children of these container types are never visible through another // symbol (i.e. through another symbol's 'exports' or 'members'). Instead, @@ -873,7 +888,7 @@ namespace ts { } } - function bindFunctionOrConstructorType(node: SignatureDeclaration) { + function bindFunctionOrConstructorTypeOrJSDocFunctionType(node: SignatureDeclaration): void { // For a given function symbol "<...>(...) => T" we want to generate a symbol identical // to the one we would get for: { <...>(...): T } // @@ -948,7 +963,7 @@ namespace ts { declareModuleMember(node, symbolFlags, symbolExcludes); break; } - // fall through. + // fall through. default: if (!blockScopeContainer.locals) { blockScopeContainer.locals = {}; @@ -1227,12 +1242,14 @@ namespace ts { return bindVariableDeclarationOrBindingElement(node); case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: + case SyntaxKind.JSDocRecordMember: return bindPropertyOrMethodOrAccessor(node, SymbolFlags.Property | ((node).questionToken ? SymbolFlags.Optional : SymbolFlags.None), SymbolFlags.PropertyExcludes); case SyntaxKind.PropertyAssignment: case SyntaxKind.ShorthandPropertyAssignment: return bindPropertyOrMethodOrAccessor(node, SymbolFlags.Property, SymbolFlags.PropertyExcludes); case SyntaxKind.EnumMember: return bindPropertyOrMethodOrAccessor(node, SymbolFlags.EnumMember, SymbolFlags.EnumMemberExcludes); + case SyntaxKind.CallSignature: case SyntaxKind.ConstructSignature: case SyntaxKind.IndexSignature: @@ -1256,8 +1273,10 @@ namespace ts { return bindPropertyOrMethodOrAccessor(node, SymbolFlags.SetAccessor, SymbolFlags.SetAccessorExcludes); case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: - return bindFunctionOrConstructorType(node); + case SyntaxKind.JSDocFunctionType: + return bindFunctionOrConstructorTypeOrJSDocFunctionType(node); case SyntaxKind.TypeLiteral: + case SyntaxKind.JSDocRecordType: return bindAnonymousDeclaration(node, SymbolFlags.TypeLiteral, "__type"); case SyntaxKind.ObjectLiteralExpression: return bindObjectLiteralExpression(node); @@ -1269,6 +1288,8 @@ namespace ts { case SyntaxKind.CallExpression: if (isInJavaScriptFile(node)) { + // We're only inspecting call expressions to detect CommonJS modules, so we can skip + // this check if we've already seen the module indicator bindCallExpression(node); } break; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fb4af5d7298a1..a2e76ea488ec8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -164,8 +164,6 @@ namespace ts { let getGlobalPromiseConstructorLikeType: () => ObjectType; let getGlobalThenableType: () => ObjectType; - let jsxElementClassType: Type; - let deferredNodes: Node[]; const tupleTypes: Map = {}; @@ -546,7 +544,9 @@ namespace ts { // - Type parameters of a function are in scope in the entire function declaration, including the parameter // list and return type. However, local types are only in scope in the function body. // - parameters are only in the scope of function body - if (meaning & result.flags & SymbolFlags.Type) { + // This restriction does not apply to JSDoc comment types because they are parented + // at a higher level than type parameters would normally be + if (meaning & result.flags & SymbolFlags.Type && lastLocation.kind !== SyntaxKind.JSDocComment) { useResult = result.flags & SymbolFlags.TypeParameter // type parameters are visible in parameter list, return type and type parameter list ? lastLocation === (location).type || @@ -2518,8 +2518,54 @@ namespace ts { return type; } + function getTypeForVariableLikeDeclarationFromJSDocComment(declaration: VariableLikeDeclaration) { + const jsDocType = getJSDocTypeForVariableLikeDeclarationFromJSDocComment(declaration); + if (jsDocType) { + return getTypeFromTypeNode(jsDocType); + } + } + + function getJSDocTypeForVariableLikeDeclarationFromJSDocComment(declaration: VariableLikeDeclaration): JSDocType { + // First, see if this node has an @type annotation on it directly. + const typeTag = getJSDocTypeTag(declaration); + if (typeTag) { + return typeTag.typeExpression.type; + } + + if (declaration.kind === SyntaxKind.VariableDeclaration && + declaration.parent.kind === SyntaxKind.VariableDeclarationList && + declaration.parent.parent.kind === SyntaxKind.VariableStatement) { + + // @type annotation might have been on the variable statement, try that instead. + const annotation = getJSDocTypeTag(declaration.parent.parent); + if (annotation) { + return annotation.typeExpression.type; + } + } + else if (declaration.kind === SyntaxKind.Parameter) { + // If it's a parameter, see if the parent has a jsdoc comment with an @param + // annotation. + const paramTag = getCorrespondingJSDocParameterTag(declaration); + if (paramTag && paramTag.typeExpression) { + return paramTag.typeExpression.type; + } + } + + return undefined; + } + // Return the inferred type for a variable, parameter, or property declaration function getTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration): Type { + if (declaration.parserContextFlags & ParserContextFlags.JavaScriptFile) { + // If this is a variable in a JavaScript file, then use the JSDoc type (if it has + // one as its type), otherwise fallback to the below standard TS codepaths to + // try to figure it out. + const type = getTypeForVariableLikeDeclarationFromJSDocComment(declaration); + if (type && type !== unknownType) { + return type; + } + } + // A variable declared in a for..in statement is always of type any if (declaration.parent.parent.kind === SyntaxKind.ForInStatement) { return anyType; @@ -3837,6 +3883,21 @@ namespace ts { return getIndexTypeOfStructuredType(getApparentType(type), kind); } + function getTypeParametersFromSignatureDeclaration(declaration: SignatureDeclaration): TypeParameter[] { + if (declaration.parserContextFlags & ParserContextFlags.JavaScriptFile) { + const templateTag = getJSDocTemplateTag(declaration); + if (templateTag) { + return getTypeParametersFromDeclaration(templateTag.typeParameters); + } + } + + if (declaration.typeParameters) { + return getTypeParametersFromDeclaration(declaration.typeParameters); + } + + return undefined; + } + // Return list of type parameters with duplicates removed (duplicate identifier errors are generated in the actual // type checking functions). function getTypeParametersFromDeclaration(typeParameterDeclarations: TypeParameterDeclaration[]): TypeParameter[] { @@ -3860,12 +3921,33 @@ namespace ts { return result; } - function isOptionalParameter(node: ParameterDeclaration) { + function isOptionalParameter(node: ParameterDeclaration, skipSignatureCheck?: boolean) { + if (node.parserContextFlags & ParserContextFlags.JavaScriptFile) { + if (node.type && node.type.kind === SyntaxKind.JSDocOptionalType) { + return true; + } + + const paramTag = getCorrespondingJSDocParameterTag(node); + if (paramTag) { + if (paramTag.isBracketed) { + return true; + } + + if (paramTag.typeExpression) { + return paramTag.typeExpression.type.kind === SyntaxKind.JSDocOptionalType; + } + } + } + if (hasQuestionToken(node)) { return true; } if (node.initializer) { + if (skipSignatureCheck) { + return true; + } + const signatureDeclaration = node.parent; const signature = getSignatureFromDeclaration(signatureDeclaration); const parameterIndex = ts.indexOf(signatureDeclaration.parameters, node); @@ -3901,12 +3983,20 @@ namespace ts { getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent).symbol)) : undefined; const typeParameters = classType ? classType.localTypeParameters : - declaration.typeParameters ? getTypeParametersFromDeclaration(declaration.typeParameters) : undefined; + declaration.typeParameters ? getTypeParametersFromDeclaration(declaration.typeParameters) : + getTypeParametersFromSignatureDeclaration(declaration); const parameters: Symbol[] = []; let hasStringLiterals = false; let minArgumentCount = -1; - for (let i = 0, n = declaration.parameters.length; i < n; i++) { + const isJSConstructSignature = isJSDocConstructSignature(declaration); + let returnType: Type = undefined; + + // If this is a JSDoc construct signature, then skip the first parameter in the + // parameter list. The first parameter represents the return type of the construct + // signature. + for (let i = isJSConstructSignature ? 1 : 0, n = declaration.parameters.length; i < n; i++) { const param = declaration.parameters[i]; + let paramSymbol = param.symbol; // Include parameter symbol instead of property symbol in the signature if (paramSymbol && !!(paramSymbol.flags & SymbolFlags.Property) && !isBindingPattern(param.name)) { @@ -3914,6 +4004,7 @@ namespace ts { paramSymbol = resolvedSymbol; } parameters.push(paramSymbol); + if (param.type && param.type.kind === SyntaxKind.StringLiteralType) { hasStringLiterals = true; } @@ -3933,14 +4024,24 @@ namespace ts { minArgumentCount = declaration.parameters.length; } - let returnType: Type; - if (classType) { + if (isJSConstructSignature) { + minArgumentCount--; + returnType = getTypeFromTypeNode(declaration.parameters[0].type); + } + else if (classType) { returnType = classType; } else if (declaration.type) { returnType = getTypeFromTypeNode(declaration.type); } else { + if (declaration.parserContextFlags & ParserContextFlags.JavaScriptFile) { + const type = getReturnTypeFromJSDocComment(declaration); + if (type && type !== unknownType) { + returnType = type; + } + } + // TypeScript 1.0 spec (April 2014): // If only one accessor includes a type annotation, the other behaves as if it had the same type annotation. if (declaration.kind === SyntaxKind.GetAccessor && !hasDynamicName(declaration)) { @@ -3977,6 +4078,7 @@ namespace ts { case SyntaxKind.SetAccessor: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: + case SyntaxKind.JSDocFunctionType: // Don't include signature if node is the implementation of an overloaded function. A node is considered // an implementation node if it has a body and the previous node is of the same kind and immediately // precedes the implementation node (i.e. has the same parent and ends where the implementation starts). @@ -4196,7 +4298,7 @@ namespace ts { } // Get type from reference to class or interface - function getTypeFromClassOrInterfaceReference(node: TypeReferenceNode | ExpressionWithTypeArguments, symbol: Symbol): Type { + function getTypeFromClassOrInterfaceReference(node: TypeReferenceNode | ExpressionWithTypeArguments | JSDocTypeReference, symbol: Symbol): Type { const type = getDeclaredTypeOfSymbol(symbol); const typeParameters = type.localTypeParameters; if (typeParameters) { @@ -4219,7 +4321,7 @@ namespace ts { // Get type from reference to type alias. When a type alias is generic, the declared type of the type alias may include // references to the type parameters of the alias. We replace those with the actual type arguments by instantiating the // declared type. Instantiations are cached using the type identities of the type arguments as the key. - function getTypeFromTypeAliasReference(node: TypeReferenceNode | ExpressionWithTypeArguments, symbol: Symbol): Type { + function getTypeFromTypeAliasReference(node: TypeReferenceNode | ExpressionWithTypeArguments | JSDocTypeReference, symbol: Symbol): Type { const type = getDeclaredTypeOfSymbol(symbol); const links = getSymbolLinks(symbol); const typeParameters = links.typeParameters; @@ -4240,7 +4342,7 @@ namespace ts { } // Get type from reference to named type that cannot be generic (enum or type parameter) - function getTypeFromNonGenericTypeReference(node: TypeReferenceNode | ExpressionWithTypeArguments, symbol: Symbol): Type { + function getTypeFromNonGenericTypeReference(node: TypeReferenceNode | ExpressionWithTypeArguments | JSDocTypeReference, symbol: Symbol): Type { if (node.typeArguments) { error(node, Diagnostics.Type_0_is_not_generic, symbolToString(symbol)); return unknownType; @@ -4248,18 +4350,90 @@ namespace ts { return getDeclaredTypeOfSymbol(symbol); } - function getTypeFromTypeReference(node: TypeReferenceNode | ExpressionWithTypeArguments): Type { + function getTypeReferenceName(node: TypeReferenceNode | ExpressionWithTypeArguments | JSDocTypeReference): LeftHandSideExpression | EntityName { + switch (node.kind) { + case SyntaxKind.TypeReference: + return (node).typeName; + case SyntaxKind.JSDocTypeReference: + return (node).name; + case SyntaxKind.ExpressionWithTypeArguments: + // We only support expressions that are simple qualified names. For other + // expressions this produces undefined. + if (isSupportedExpressionWithTypeArguments(node)) { + return (node).expression; + } + + // fall through; + } + + return undefined; + } + + function resolveTypeReferenceName( + node: TypeReferenceNode | ExpressionWithTypeArguments | JSDocTypeReference, + typeReferenceName: LeftHandSideExpression | EntityName) { + + if (!typeReferenceName) { + return unknownSymbol; + } + + let symbol = resolveEntityName(typeReferenceName, SymbolFlags.Type); + if (!symbol && node.kind === SyntaxKind.JSDocTypeReference) { + // If the reference didn't resolve to a type, try seeing if results to a + // value. If it does, get the type of that value. + symbol = resolveEntityName(typeReferenceName, SymbolFlags.Value); + } + + return symbol || unknownSymbol; + } + + function getTypeReferenceType(node: TypeReferenceNode | ExpressionWithTypeArguments | JSDocTypeReference, symbol: Symbol) { + if (symbol === unknownSymbol) { + return unknownType; + } + + if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { + return getTypeFromClassOrInterfaceReference(node, symbol); + } + + if (symbol.flags & SymbolFlags.TypeAlias) { + return getTypeFromTypeAliasReference(node, symbol); + } + + if (symbol.flags & SymbolFlags.Value && node.kind === SyntaxKind.JSDocTypeReference) { + // A JSDocTypeReference may have resolved to a value (as opposed to a type). In + // that case, the type of this reference is just the type of the value we resolved + // to. + return getTypeOfSymbol(symbol); + } + + return getTypeFromNonGenericTypeReference(node, symbol); + } + + function getTypeFromTypeReference(node: TypeReferenceNode | ExpressionWithTypeArguments | JSDocTypeReference): Type { const links = getNodeLinks(node); if (!links.resolvedType) { - // We only support expressions that are simple qualified names. For other expressions this produces undefined. - const typeNameOrExpression = node.kind === SyntaxKind.TypeReference ? (node).typeName : - isSupportedExpressionWithTypeArguments(node) ? (node).expression : - undefined; - const symbol = typeNameOrExpression && resolveEntityName(typeNameOrExpression, SymbolFlags.Type) || unknownSymbol; - const type = symbol === unknownSymbol ? unknownType : - symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface) ? getTypeFromClassOrInterfaceReference(node, symbol) : - symbol.flags & SymbolFlags.TypeAlias ? getTypeFromTypeAliasReference(node, symbol) : - getTypeFromNonGenericTypeReference(node, symbol); + let symbol: Symbol; + let type: Type; + if (node.kind === SyntaxKind.JSDocTypeReference) { + const typeReferenceName = getTypeReferenceName(node); + symbol = resolveTypeReferenceName(node, typeReferenceName); + type = getTypeReferenceType(node, symbol); + + links.resolvedSymbol = symbol; + links.resolvedType = type; + } + else { + // We only support expressions that are simple qualified names. For other expressions this produces undefined. + const typeNameOrExpression = node.kind === SyntaxKind.TypeReference ? (node).typeName : + isSupportedExpressionWithTypeArguments(node) ? (node).expression : + undefined; + symbol = typeNameOrExpression && resolveEntityName(typeNameOrExpression, SymbolFlags.Type) || unknownSymbol; + type = symbol === unknownSymbol ? unknownType : + symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface) ? getTypeFromClassOrInterfaceReference(node, symbol) : + symbol.flags & SymbolFlags.TypeAlias ? getTypeFromTypeAliasReference(node, symbol) : + getTypeFromNonGenericTypeReference(node, symbol); + } // Cache both the resolved symbol and the resolved type. The resolved symbol is needed in when we check the // type reference in checkTypeReferenceOrExpressionWithTypeArguments. links.resolvedSymbol = symbol; @@ -4554,6 +4728,62 @@ namespace ts { return links.resolvedType; } + function getTypeFromJSDocFunctionType(node: JSDocFunctionType): Type { + Debug.assert(!!node.symbol); + const links = getNodeLinks(node); + if (!links.resolvedType) { + links.resolvedType = createObjectType(TypeFlags.Anonymous, node.symbol); + } + return links.resolvedType; + } + + function getTypeFromJSDocRecordType(node: JSDocRecordType): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + links.resolvedType = createObjectType(TypeFlags.Anonymous, node.symbol); + } + return links.resolvedType; + } + + function getTypeFromJSDocVariadicType(node: JSDocVariadicType): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const type = getTypeFromTypeNode(node.type); + links.resolvedType = type ? createArrayType(type) : unknownType; + } + return links.resolvedType; + } + + function getTypeFromJSDocTypeReference(node: JSDocTypeReference): Type { + return getTypeFromTypeReference(node); + } + + function getTypeFromJSDocArrayType(node: JSDocArrayType): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + links.resolvedType = createArrayType(getTypeFromTypeNode(node.elementType)); + } + return links.resolvedType; + } + + function getTypeFromJSDocUnionType(node: JSDocUnionType): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const types = map(node.types, getTypeFromTypeNode); + links.resolvedType = getUnionType(types, /*noSubtypeReduction*/ true); + } + return links.resolvedType; + } + + function getTypeFromJSDocTupleType(node: JSDocTupleType): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const types = map(node.types, getTypeFromTypeNode); + links.resolvedType = createTupleType(types); + } + return links.resolvedType; + } + function getThisType(node: TypeNode): Type { const container = getThisContainer(node, /*includeArrowFunctions*/ false); const parent = container && container.parent; @@ -4640,6 +4870,34 @@ namespace ts { case SyntaxKind.QualifiedName: const symbol = getSymbolAtLocation(node); return symbol && getDeclaredTypeOfSymbol(symbol); + case SyntaxKind.JSDocAllType: + return anyType; + case SyntaxKind.JSDocUnknownType: + return unknownType; + case SyntaxKind.JSDocArrayType: + return getTypeFromJSDocArrayType(node); + case SyntaxKind.JSDocTupleType: + return getTypeFromJSDocTupleType(node); + case SyntaxKind.JSDocUnionType: + return getTypeFromJSDocUnionType(node); + case SyntaxKind.JSDocNullableType: + return getTypeFromTypeNode((node).type); + case SyntaxKind.JSDocNonNullableType: + return getTypeFromTypeNode((node).type); + case SyntaxKind.JSDocTypeReference: + return getTypeFromJSDocTypeReference(node); + case SyntaxKind.JSDocOptionalType: + return getTypeFromTypeNode((node).type); + case SyntaxKind.JSDocFunctionType: + return getTypeFromJSDocFunctionType(node); + case SyntaxKind.JSDocVariadicType: + return getTypeFromJSDocVariadicType(node); + case SyntaxKind.JSDocConstructorType: + return getTypeFromTypeNode((node).type); + case SyntaxKind.JSDocRecordType: + return getTypeFromJSDocRecordType(node); + case SyntaxKind.JSDocThisType: + return getTypeFromTypeNode((node).type); default: return unknownType; } @@ -7062,6 +7320,13 @@ namespace ts { return container.flags & NodeFlags.Static ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol)).thisType; } + if (container.parserContextFlags & ParserContextFlags.JavaScriptFile) { + const type = getTypeForThisExpressionFromJSDoc(container); + if (type && type !== unknownType) { + return type; + } + } + // 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 (isInJavaScriptFile(node) && container.kind === SyntaxKind.FunctionExpression) { @@ -7081,6 +7346,16 @@ namespace ts { return anyType; } + function getTypeForThisExpressionFromJSDoc(node: Node) { + const typeTag = getJSDocTypeTag(node); + if (typeTag && typeTag.typeExpression.type.kind === SyntaxKind.JSDocFunctionType) { + const jsDocFunctionType = typeTag.typeExpression.type; + if (jsDocFunctionType.parameters.length > 0 && jsDocFunctionType.parameters[0].type.kind === SyntaxKind.JSDocThisType) { + return getTypeFromTypeNode(jsDocFunctionType.parameters[0].type); + } + } + } + function isInConstructorArgumentInitializer(node: Node, constructorDecl: Node): boolean { for (let n = node; n && n !== constructorDecl; n = n.parent) { if (n.kind === SyntaxKind.Parameter) { @@ -7227,7 +7502,6 @@ namespace ts { if (isContextSensitive(func)) { const contextualSignature = getContextualSignature(func); if (contextualSignature) { - const funcHasRestParameters = hasRestParameter(func); const len = func.parameters.length - (funcHasRestParameters ? 1 : 0); const indexOfParameter = indexOf(func.parameters, parameter); @@ -8350,10 +8624,7 @@ namespace ts { } function getJsxGlobalElementClassType(): Type { - if (!jsxElementClassType) { - jsxElementClassType = getExportedTypeFromNamespace(JsxNames.JSX, JsxNames.ElementClass); - } - return jsxElementClassType; + return getExportedTypeFromNamespace(JsxNames.JSX, JsxNames.ElementClass); } /// Returns all the properties of the Jsx.IntrinsicElements interface @@ -9155,32 +9426,33 @@ namespace ts { */ function getEffectiveDecoratorFirstArgumentType(node: Node): Type { // The first argument to a decorator is its `target`. - if (node.kind === SyntaxKind.ClassDeclaration) { - // For a class decorator, the `target` is the type of the class (e.g. the - // "static" or "constructor" side of the class) - const classSymbol = getSymbolOfNode(node); - return getTypeOfSymbol(classSymbol); - } - - if (node.kind === SyntaxKind.Parameter) { - // For a parameter decorator, the `target` is the parent type of the - // parameter's containing method. - node = node.parent; - if (node.kind === SyntaxKind.Constructor) { + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + // For a class decorator, the `target` is the type of the class (e.g. the + // "static" or "constructor" side of the class) const classSymbol = getSymbolOfNode(node); return getTypeOfSymbol(classSymbol); - } - } - if (node.kind === SyntaxKind.PropertyDeclaration || - node.kind === SyntaxKind.MethodDeclaration || - node.kind === SyntaxKind.GetAccessor || - node.kind === SyntaxKind.SetAccessor) { - // For a property or method decorator, the `target` is the - // "static"-side type of the parent of the member if the member is - // declared "static"; otherwise, it is the "instance"-side type of the - // parent of the member. - return getParentTypeOfClassElement(node); + case SyntaxKind.Parameter: + // For a parameter decorator, the `target` is the parent type of the + // parameter's containing method. + node = node.parent; + if (node.kind === SyntaxKind.Constructor) { + const classSymbol = getSymbolOfNode(node); + return getTypeOfSymbol(classSymbol); + } + + // fall-through + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + // For a property or method decorator, the `target` is the + // "static"-side type of the parent of the member if the member is + // declared "static"; otherwise, it is the "instance"-side type of the + // parent of the member. + return getParentTypeOfClassElement(node); } Debug.fail("Unsupported decorator target."); @@ -9849,7 +10121,8 @@ namespace ts { if (declaration && declaration.kind !== SyntaxKind.Constructor && declaration.kind !== SyntaxKind.ConstructSignature && - declaration.kind !== SyntaxKind.ConstructorType) { + declaration.kind !== SyntaxKind.ConstructorType && + !isJSDocConstructSignature(declaration)) { // When resolved signature is a call signature (and not a construct signature) the result type is any, unless // the declaring function had members created through 'x.prototype.y = expr' or 'this.y = expr' psuedodeclarations @@ -9969,6 +10242,13 @@ namespace ts { } } + function getReturnTypeFromJSDocComment(func: SignatureDeclaration | FunctionDeclaration): Type { + const returnTag = getJSDocReturnTag(func); + if (returnTag) { + return getTypeFromTypeNode(returnTag.typeExpression.type); + } + } + function createPromiseType(promisedType: Type): Type { // creates a `Promise` type where `T` is the promisedType argument const globalPromiseType = getGlobalPromiseType(); @@ -9982,11 +10262,11 @@ namespace ts { } function getReturnTypeFromBody(func: FunctionLikeDeclaration, contextualMapper?: TypeMapper): Type { - const contextualSignature = getContextualSignatureForFunctionLikeDeclaration(func); if (!func.body) { return unknownType; } + const contextualSignature = getContextualSignatureForFunctionLikeDeclaration(func); const isAsync = isAsyncFunctionLike(func); let type: Type; if (func.body.kind !== SyntaxKind.Block) { diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 7f5d052a7e719..3dfe098557549 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -611,44 +611,24 @@ namespace ts { fixupParentReferences(sourceFile); } - // If this is a javascript file, proactively see if we can get JSDoc comments for - // relevant nodes in the file. We'll use these to provide typing informaion if they're - // available. - if (isSourceFileJavaScript(sourceFile)) { - addJSDocComments(); - } - return sourceFile; } - function addJSDocComments() { - forEachChild(sourceFile, visit); - return; - - function visit(node: Node) { - // Add additional cases as necessary depending on how we see JSDoc comments used - // in the wild. - switch (node.kind) { - case SyntaxKind.VariableStatement: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.Parameter: - addJSDocComment(node); - } - - forEachChild(node, visit); - } - } - function addJSDocComment(node: Node) { - const comments = getLeadingCommentRangesOfNode(node, sourceFile); - if (comments) { - for (const comment of comments) { - const jsDocComment = JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos); - if (jsDocComment) { - node.jsDocComment = jsDocComment; + function addJSDocComment(node: T): T { + if (contextFlags & ParserContextFlags.JavaScriptFile) { + const comments = getLeadingCommentRangesOfNode(node, sourceFile); + if (comments) { + for (const comment of comments) { + const jsDocComment = JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos); + if (jsDocComment) { + node.jsDocComment = jsDocComment; + } } } } + + return node; } export function fixupParentReferences(sourceFile: Node) { @@ -2068,7 +2048,8 @@ namespace ts { // contexts. In addition, parameter initializers are semantically disallowed in // overload signatures. So parameter initializers are transitively disallowed in // ambient contexts. - return finishNode(node); + + return addJSDocComment(finishNode(node)); } function parseBindingElementInitializer(inParameter: boolean) { @@ -4724,7 +4705,7 @@ namespace ts { setModifiers(node, modifiers); node.declarationList = parseVariableDeclarationList(/*inForStatementInitializer*/ false); parseSemicolon(); - return finishNode(node); + return addJSDocComment(finishNode(node)); } function parseFunctionDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): FunctionDeclaration { @@ -4738,7 +4719,7 @@ namespace ts { const isAsync = !!(node.flags & NodeFlags.Async); fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ isGenerator, /*awaitContext*/ isAsync, /*requireCompleteParameterList*/ false, node); node.body = parseFunctionBlockOrSemicolon(isGenerator, isAsync, Diagnostics.or_expected); - return finishNode(node); + return addJSDocComment(finishNode(node)); } function parseConstructorDeclaration(pos: number, decorators: NodeArray, modifiers: ModifiersArray): ConstructorDeclaration { @@ -5564,23 +5545,19 @@ namespace ts { export function parseJSDocTypeExpressionForTests(content: string, start: number, length: number) { initializeState("file.js", content, ScriptTarget.Latest, /*isJavaScriptFile*/ true, /*_syntaxCursor:*/ undefined); - const jsDocTypeExpression = parseJSDocTypeExpression(start, length); + scanner.setText(content, start, length); + token = scanner.scan(); + const jsDocTypeExpression = parseJSDocTypeExpression(); const diagnostics = parseDiagnostics; clearState(); return jsDocTypeExpression ? { jsDocTypeExpression, diagnostics } : undefined; } - // Parses out a JSDoc type expression. The starting position should be right at the open - // curly in the type expression. Returns 'undefined' if it encounters any errors while parsing. + // Parses out a JSDoc type expression. /* @internal */ - export function parseJSDocTypeExpression(start: number, length: number): JSDocTypeExpression { - scanner.setText(sourceText, start, length); - - // Prime the first token for us to start processing. - token = nextToken(); - - const result = createNode(SyntaxKind.JSDocTypeExpression); + export function parseJSDocTypeExpression(): JSDocTypeExpression { + const result = createNode(SyntaxKind.JSDocTypeExpression, scanner.getTokenPos()); parseExpected(SyntaxKind.OpenBraceToken); result.type = parseJSDocTopLevelType(); @@ -5878,7 +5855,8 @@ namespace ts { export function parseIsolatedJSDocComment(content: string, start: number, length: number) { initializeState("file.js", content, ScriptTarget.Latest, /*isJavaScriptFile*/ true, /*_syntaxCursor:*/ undefined); - const jsDocComment = parseJSDocComment(/*parent:*/ undefined, start, length); + sourceFile = { languageVariant: LanguageVariant.Standard, text: content }; + const jsDocComment = parseJSDocCommentWorker(start, length); const diagnostics = parseDiagnostics; clearState(); @@ -5886,12 +5864,19 @@ namespace ts { } export function parseJSDocComment(parent: Node, start: number, length: number): JSDocComment { + const saveToken = token; + const saveParseDiagnosticsLength = parseDiagnostics.length; + const saveParseErrorBeforeNextFinishedNode = parseErrorBeforeNextFinishedNode; + const comment = parseJSDocCommentWorker(start, length); if (comment) { - fixupParentReferences(comment); comment.parent = parent; } + token = saveToken; + parseDiagnostics.length = saveParseDiagnosticsLength; + parseErrorBeforeNextFinishedNode = saveParseErrorBeforeNextFinishedNode; + return comment; } @@ -5906,69 +5891,69 @@ namespace ts { Debug.assert(end <= content.length); let tags: NodeArray; - let pos: number; - - // NOTE(cyrusn): This is essentially a handwritten scanner for JSDocComments. I - // considered using an actual Scanner, but this would complicate things. The - // scanner would need to know it was in a Doc Comment. Otherwise, it would then - // produce comments *inside* the doc comment. In the end it was just easier to - // write a simple scanner rather than go that route. - if (length >= "/** */".length) { - if (content.charCodeAt(start) === CharacterCodes.slash && - content.charCodeAt(start + 1) === CharacterCodes.asterisk && - content.charCodeAt(start + 2) === CharacterCodes.asterisk && - content.charCodeAt(start + 3) !== CharacterCodes.asterisk) { + let result: JSDocComment; + + // Check for /** (JSDoc opening part) + if (content.charCodeAt(start) === CharacterCodes.slash && + content.charCodeAt(start + 1) === CharacterCodes.asterisk && + content.charCodeAt(start + 2) === CharacterCodes.asterisk && + content.charCodeAt(start + 3) !== CharacterCodes.asterisk) { + + + // + 3 for leading /**, - 5 in total for /** */ + scanner.scanRange(start + 3, length - 5, () => { // Initially we can parse out a tag. We also have seen a starting asterisk. // This is so that /** * @type */ doesn't parse. let canParseTag = true; let seenAsterisk = true; - for (pos = start + "/**".length; pos < end; ) { - const ch = content.charCodeAt(pos); - pos++; - - if (ch === CharacterCodes.at && canParseTag) { - parseTag(); - - // Once we parse out a tag, we cannot keep parsing out tags on this line. - canParseTag = false; - continue; - } + nextJSDocToken(); + while (token !== SyntaxKind.EndOfFileToken) { + switch (token) { + case SyntaxKind.AtToken: + if (canParseTag) { + parseTag(); + } + // This will take us to the end of the line, so it's OK to parse a tag on the next pass through the loop + seenAsterisk = false; + break; + + case SyntaxKind.NewLineTrivia: + // After a line break, we can parse a tag, and we haven't seen an asterisk on the next line yet + canParseTag = true; + seenAsterisk = false; + break; + + case SyntaxKind.AsteriskToken: + if (seenAsterisk) { + // If we've already seen an asterisk, then we can no longer parse a tag on this line + canParseTag = false; + } + // Ignore the first asterisk on a line + seenAsterisk = true; + break; + + case SyntaxKind.Identifier: + // Anything else is doc comment text. We can't do anything with it. Because it + // wasn't a tag, we can no longer parse a tag on this line until we hit the next + // line break. + canParseTag = false; + break; - if (isLineBreak(ch)) { - // After a line break, we can parse a tag, and we haven't seen as asterisk - // on the next line yet. - canParseTag = true; - seenAsterisk = false; - continue; + case SyntaxKind.EndOfFileToken: + break; } - if (isWhiteSpace(ch)) { - // Whitespace doesn't affect any of our parsing. - continue; - } + nextJSDocToken(); + } - // Ignore the first asterisk on a line. - if (ch === CharacterCodes.asterisk) { - if (seenAsterisk) { - // If we've already seen an asterisk, then we can no longer parse a tag - // on this line. - canParseTag = false; - } - seenAsterisk = true; - continue; - } + result = createJSDocComment(); - // Anything else is doc comment text. We can't do anything with it. Because it - // wasn't a tag, we can no longer parse a tag on this line until we hit the next - // line break. - canParseTag = false; - } - } + }); } - return createJSDocComment(); + return result; function createJSDocComment(): JSDocComment { if (!tags) { @@ -5981,21 +5966,23 @@ namespace ts { } function skipWhitespace(): void { - while (pos < end && isWhiteSpace(content.charCodeAt(pos))) { - pos++; + while (token === SyntaxKind.WhitespaceTrivia || token === SyntaxKind.NewLineTrivia) { + nextJSDocToken(); } } function parseTag(): void { - Debug.assert(content.charCodeAt(pos - 1) === CharacterCodes.at); - const atToken = createNode(SyntaxKind.AtToken, pos - 1); - atToken.end = pos; + Debug.assert(token === SyntaxKind.AtToken); + const atToken = createNode(SyntaxKind.AtToken, scanner.getTokenPos()); + atToken.end = scanner.getTextPos(); + nextJSDocToken(); - const tagName = scanIdentifier(); + const tagName = scanJsDocIdentifier(); if (!tagName) { return; } + nextJSDocToken(); const tag = handleTag(atToken, tagName) || handleUnknownTag(atToken, tagName); addTag(tag); } @@ -6022,7 +6009,7 @@ namespace ts { const result = createNode(SyntaxKind.JSDocTag, atToken.pos); result.atToken = atToken; result.tagName = tagName; - return finishNode(result, pos); + return finishNode(result); } function addTag(tag: JSDocTag): void { @@ -6038,14 +6025,11 @@ namespace ts { } function tryParseTypeExpression(): JSDocTypeExpression { - skipWhitespace(); - - if (content.charCodeAt(pos) !== CharacterCodes.openBrace) { + if (token !== SyntaxKind.OpenBraceToken) { return undefined; } - const typeExpression = parseJSDocTypeExpression(pos, end - pos); - pos = typeExpression.end; + const typeExpression = parseJSDocTypeExpression(); return typeExpression; } @@ -6055,18 +6039,27 @@ namespace ts { skipWhitespace(); let name: Identifier; let isBracketed: boolean; - if (content.charCodeAt(pos) === CharacterCodes.openBracket) { - pos++; - skipWhitespace(); - name = scanIdentifier(); + // Looking for something like '[foo]' or 'foo' + if (parseOptionalToken(SyntaxKind.OpenBracketToken)) { + name = scanJsDocIdentifier(); + nextJSDocToken(); isBracketed = true; + + // May have an optional default, e.g. '[foo = 42]' + if (parseOptionalToken(SyntaxKind.EqualsToken)) { + parseExpression(); + } + + parseExpected(SyntaxKind.CloseBracketToken); } - else { - name = scanIdentifier(); + else if (token === SyntaxKind.Identifier) { + name = scanJsDocIdentifier(); + nextJSDocToken(); } if (!name) { - parseErrorAtPosition(pos, 0, Diagnostics.Identifier_expected); + parseErrorAtPosition(scanner.getStartPos(), 0, Diagnostics.Identifier_expected); + return undefined; } let preName: Identifier, postName: Identifier; @@ -6088,95 +6081,88 @@ namespace ts { result.typeExpression = typeExpression; result.postParameterName = postName; result.isBracketed = isBracketed; - return finishNode(result, pos); + return finishNode(result); } function handleReturnTag(atToken: Node, tagName: Identifier): JSDocReturnTag { if (forEach(tags, t => t.kind === SyntaxKind.JSDocReturnTag)) { - parseErrorAtPosition(tagName.pos, pos - tagName.pos, Diagnostics._0_tag_already_specified, tagName.text); + parseErrorAtPosition(tagName.pos, scanner.getTokenPos() - tagName.pos, Diagnostics._0_tag_already_specified, tagName.text); } const result = createNode(SyntaxKind.JSDocReturnTag, atToken.pos); result.atToken = atToken; result.tagName = tagName; result.typeExpression = tryParseTypeExpression(); - return finishNode(result, pos); + return finishNode(result); } function handleTypeTag(atToken: Node, tagName: Identifier): JSDocTypeTag { if (forEach(tags, t => t.kind === SyntaxKind.JSDocTypeTag)) { - parseErrorAtPosition(tagName.pos, pos - tagName.pos, Diagnostics._0_tag_already_specified, tagName.text); + parseErrorAtPosition(tagName.pos, scanner.getTokenPos() - tagName.pos, Diagnostics._0_tag_already_specified, tagName.text); } const result = createNode(SyntaxKind.JSDocTypeTag, atToken.pos); result.atToken = atToken; result.tagName = tagName; result.typeExpression = tryParseTypeExpression(); - return finishNode(result, pos); + return finishNode(result); } function handleTemplateTag(atToken: Node, tagName: Identifier): JSDocTemplateTag { if (forEach(tags, t => t.kind === SyntaxKind.JSDocTemplateTag)) { - parseErrorAtPosition(tagName.pos, pos - tagName.pos, Diagnostics._0_tag_already_specified, tagName.text); + parseErrorAtPosition(tagName.pos, scanner.getTokenPos() - tagName.pos, Diagnostics._0_tag_already_specified, tagName.text); } + // Type parameter list looks like '@template T,U,V' const typeParameters = >[]; - typeParameters.pos = pos; + typeParameters.pos = scanner.getStartPos(); while (true) { - skipWhitespace(); - - const startPos = pos; - const name = scanIdentifier(); + const name = scanJsDocIdentifier(); if (!name) { - parseErrorAtPosition(startPos, 0, Diagnostics.Identifier_expected); + parseErrorAtPosition(scanner.getStartPos(), 0, Diagnostics.Identifier_expected); return undefined; } const typeParameter = createNode(SyntaxKind.TypeParameter, name.pos); typeParameter.name = name; - finishNode(typeParameter, pos); + nextJSDocToken(); + finishNode(typeParameter); typeParameters.push(typeParameter); - skipWhitespace(); - if (content.charCodeAt(pos) !== CharacterCodes.comma) { + if (token === SyntaxKind.CommaToken) { + nextJSDocToken(); + } + else { break; } - - pos++; } - typeParameters.end = pos; - const result = createNode(SyntaxKind.JSDocTemplateTag, atToken.pos); result.atToken = atToken; result.tagName = tagName; result.typeParameters = typeParameters; - return finishNode(result, pos); + finishNode(result); + typeParameters.end = result.end; + return result; } - function scanIdentifier(): Identifier { - const startPos = pos; - for (; pos < end; pos++) { - const ch = content.charCodeAt(pos); - if (pos === startPos && isIdentifierStart(ch, ScriptTarget.Latest)) { - continue; - } - else if (pos > startPos && isIdentifierPart(ch, ScriptTarget.Latest)) { - continue; - } - - break; - } + function nextJSDocToken(): SyntaxKind { + return token = scanner.scanJSDocToken(); + } - if (startPos === pos) { + function scanJsDocIdentifier(): Identifier { + if (token !== SyntaxKind.Identifier) { + parseErrorAtCurrentToken(Diagnostics.Identifier_expected); return undefined; } - const result = createNode(SyntaxKind.Identifier, startPos); - result.text = content.substring(startPos, pos); - return finishNode(result, pos); + const pos = scanner.getTokenPos(); + const end = scanner.getTextPos(); + const result = createNode(SyntaxKind.Identifier, pos); + result.text = content.substring(pos, end); + return finishNode(result, end); } } } diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 6c0489ff1dcd7..147bfe049ea6c 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -29,6 +29,7 @@ namespace ts { scanJsxIdentifier(): SyntaxKind; reScanJsxToken(): SyntaxKind; scanJsxToken(): SyntaxKind; + scanJSDocToken(): SyntaxKind; scan(): SyntaxKind; // Sets the text for the scanner to scan. An optional subrange starting point and length // can be provided to have the scanner only scan a portion of the text. @@ -42,6 +43,10 @@ namespace ts { // is returned from this function. lookAhead(callback: () => T): T; + // Invokes the callback with the scanner set to scan the specified range. When the callback + // returns, the scanner is restored to the state it was in before scanRange was called. + scanRange(start: number, length: number, callback: () => T): T; + // Invokes the provided callback. If the callback returns something falsy, then it restores // the scanner to the state it was in immediately prior to invoking the callback. If the // callback returns something truthy, then the scanner state is not rolled back. The result @@ -734,6 +739,7 @@ namespace ts { scanJsxIdentifier, reScanJsxToken, scanJsxToken, + scanJSDocToken, scan, setText, setScriptTarget, @@ -742,6 +748,7 @@ namespace ts { setTextPos, tryScan, lookAhead, + scanRange, }; function error(message: DiagnosticMessage, length?: number): void { @@ -1649,6 +1656,69 @@ namespace ts { return token; } + function scanJSDocToken(): SyntaxKind { + startPos = pos; + + // Eat leading whitespace + while (pos < end) { + const ch = text.charCodeAt(pos); + if (isWhiteSpace(ch)) { + pos++; + } + else { + break; + } + } + tokenPos = pos; + + let identifierStarted = false; + while (pos < end) { + const ch = text.charCodeAt(pos); + if (identifierStarted) { + if (!isIdentifierPart(ch, ScriptTarget.Latest)) { + return token = SyntaxKind.Identifier; + } + } + else { + if (ch === CharacterCodes.at) { + return pos += 1, token = SyntaxKind.AtToken; + } + else if (isLineBreak(ch)) { + return pos += 1, token = SyntaxKind.NewLineTrivia; + } + else if (ch === CharacterCodes.asterisk) { + return pos += 1, token = SyntaxKind.AsteriskToken; + } + else if (ch === CharacterCodes.openBrace) { + return pos += 1, token = SyntaxKind.OpenBraceToken; + } + else if (ch === CharacterCodes.closeBrace) { + return pos += 1, token = SyntaxKind.CloseBraceToken; + } + else if (ch === CharacterCodes.openBracket) { + return pos += 1, token = SyntaxKind.OpenBracketToken; + } + else if (ch === CharacterCodes.closeBracket) { + return pos += 1, token = SyntaxKind.CloseBracketToken; + } + else if (ch === CharacterCodes.equals) { + return pos += 1, token = SyntaxKind.EqualsToken; + } + else if (ch === CharacterCodes.comma) { + return pos += 1, token = SyntaxKind.CommaToken; + } + else if (isWhiteSpace(ch)) { + // Keep going + } + else { + identifierStarted = true; + } + } + pos += 1; + } + return token = SyntaxKind.EndOfFileToken; + } + function speculationHelper(callback: () => T, isLookahead: boolean): T { const savePos = pos; const saveStartPos = startPos; @@ -1671,6 +1741,33 @@ namespace ts { return result; } + function scanRange(start: number, length: number, callback: () => T): T { + const saveEnd = end; + const savePos = pos; + const saveStartPos = startPos; + const saveTokenPos = tokenPos; + const saveToken = token; + const savePrecedingLineBreak = precedingLineBreak; + const saveTokenValue = tokenValue; + const saveHasExtendedUnicodeEscape = hasExtendedUnicodeEscape; + const saveTokenIsUnterminated = tokenIsUnterminated; + + setText(text, start, length); + const result = callback(); + + end = saveEnd; + pos = savePos; + startPos = saveStartPos; + tokenPos = saveTokenPos; + token = saveToken; + precedingLineBreak = savePrecedingLineBreak; + tokenValue = saveTokenValue; + hasExtendedUnicodeEscape = saveHasExtendedUnicodeEscape; + tokenIsUnterminated = saveTokenIsUnterminated; + + return result; + } + function lookAhead(callback: () => T): T { return speculationHelper(callback, /*isLookahead*/ true); } diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index c99c28bc0dd06..83b6496bb39ec 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -499,8 +499,6 @@ namespace ts { return getWScriptSystem(); } else if (typeof process !== "undefined" && process.nextTick && !process.browser && typeof require !== "undefined") { - // process and process.nextTick checks if current environment is node-like - // process.browser check excludes webpack and browserify return getNodeSystem(); } else if (typeof ChakraHost !== "undefined") { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 24f70373a8ace..a5a30f1c092fe 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -311,11 +311,11 @@ namespace ts { // Top-level nodes SourceFile, - // JSDoc nodes. + // JSDoc nodes JSDocTypeExpression, - // The * type. + // The * type JSDocAllType, - // The ? type. + // The ? type JSDocUnknownType, JSDocArrayType, JSDocUnionType, @@ -996,7 +996,7 @@ namespace ts { } // @kind(SyntaxKind.CallExpression) - export interface CallExpression extends LeftHandSideExpression { + export interface CallExpression extends LeftHandSideExpression, Declaration { expression: LeftHandSideExpression; typeArguments?: NodeArray; arguments: NodeArray; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 7bc708c717803..4e92c7e529d6e 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1054,7 +1054,7 @@ namespace ts { /** * Returns true if the node is a CallExpression to the identifier 'require' with - * exactly one string literal argument. + * exactly one argument. * This function does not test if the node is in a JavaScript file or not. */ export function isRequireCall(expression: Node): expression is CallExpression { @@ -1062,8 +1062,7 @@ namespace ts { return expression.kind === SyntaxKind.CallExpression && (expression).expression.kind === SyntaxKind.Identifier && ((expression).expression).text === "require" && - (expression).arguments.length === 1 && - (expression).arguments[0].kind === SyntaxKind.StringLiteral; + (expression).arguments.length === 1; } /// Given a BinaryExpression, returns SpecialPropertyAssignmentKind for the various kinds of property @@ -1140,26 +1139,56 @@ namespace ts { (node).parameters[0].type.kind === SyntaxKind.JSDocConstructorType; } - function getJSDocTag(node: Node, kind: SyntaxKind): JSDocTag { - if (node && node.jsDocComment) { - for (const tag of node.jsDocComment.tags) { - if (tag.kind === kind) { - return tag; - } + function getJSDocTag(node: Node, kind: SyntaxKind, checkParentVariableStatement: boolean): JSDocTag { + if (!node) { + return undefined; + } + + const jsDocComment = getJSDocComment(node, checkParentVariableStatement); + if (!jsDocComment) { + return undefined; + } + + for (const tag of jsDocComment.tags) { + if (tag.kind === kind) { + return tag; } } } + function getJSDocComment(node: Node, checkParentVariableStatement: boolean): JSDocComment { + if (node.jsDocComment) { + return node.jsDocComment; + } + // Try to recognize this pattern when node is initializer of variable declaration and JSDoc comments are on containing variable statement. + // /** + // * @param {number} name + // * @returns {number} + // */ + // var x = function(name) { return name.length; } + if (checkParentVariableStatement) { + const isInitializerOfVariableDeclarationInStatement = + node.parent.kind === SyntaxKind.VariableDeclaration && + (node.parent).initializer === node && + node.parent.parent.parent.kind === SyntaxKind.VariableStatement; + + const variableStatementNode = isInitializerOfVariableDeclarationInStatement ? node.parent.parent.parent : undefined; + return variableStatementNode && variableStatementNode.jsDocComment; + } + + return undefined; + } + export function getJSDocTypeTag(node: Node): JSDocTypeTag { - return getJSDocTag(node, SyntaxKind.JSDocTypeTag); + return getJSDocTag(node, SyntaxKind.JSDocTypeTag, /*checkParentVariableStatement*/ false); } export function getJSDocReturnTag(node: Node): JSDocReturnTag { - return getJSDocTag(node, SyntaxKind.JSDocReturnTag); + return getJSDocTag(node, SyntaxKind.JSDocReturnTag, /*checkParentVariableStatement*/ true); } export function getJSDocTemplateTag(node: Node): JSDocTemplateTag { - return getJSDocTag(node, SyntaxKind.JSDocTemplateTag); + return getJSDocTag(node, SyntaxKind.JSDocTemplateTag, /*checkParentVariableStatement*/ false); } export function getCorrespondingJSDocParameterTag(parameter: ParameterDeclaration): JSDocParameterTag { @@ -1168,19 +1197,21 @@ namespace ts { // annotation. const parameterName = (parameter.name).text; - const docComment = parameter.parent.jsDocComment; - if (docComment) { - return forEach(docComment.tags, t => { - if (t.kind === SyntaxKind.JSDocParameterTag) { - const parameterTag = t; + const jsDocComment = getJSDocComment(parameter.parent, /*checkParentVariableStatement*/ true); + if (jsDocComment) { + for (const tag of jsDocComment.tags) { + if (tag.kind === SyntaxKind.JSDocParameterTag) { + const parameterTag = tag; const name = parameterTag.preParameterName || parameterTag.postParameterName; if (name.text === parameterName) { - return t; + return parameterTag; } } - }); + } } } + + return undefined; } export function hasRestParameter(s: SignatureDeclaration): boolean { diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 8cd1bca876e08..3018f08abe5f7 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -571,6 +571,31 @@ namespace FourSlash { } } + public verifyCompletionListStartsWithItemsInOrder(items: string[]): void { + if (items.length === 0) { + return; + } + + const entries = this.getCompletionListAtCaret().entries; + assert.isTrue(items.length <= entries.length, `Amount of expected items in completion list [ ${items.length} ] is greater than actual number of items in list [ ${entries.length} ]`); + for (let i = 0; i < items.length; i++) { + assert.equal(entries[i].name, items[i], `Unexpected item in completion list`); + } + } + + public noItemsWithSameNameButDifferentKind(): void { + const completions = this.getCompletionListAtCaret(); + const uniqueItems: ts.Map = {}; + for (const item of completions.entries) { + if (!ts.hasProperty(uniqueItems, item.name)) { + uniqueItems[item.name] = item.kind; + } + else { + assert.equal(item.kind, uniqueItems[item.name], `Items should have the same kind, got ${item.kind} and ${uniqueItems[item.name]}`); + } + } + } + public verifyMemberListIsEmpty(negative: boolean) { const members = this.getMemberListAtCaret(); if ((!members || members.entries.length === 0) && negative) { diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 7fd819731721a..329834cb15d4c 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -1593,7 +1593,7 @@ namespace Harness { return { unitName: libFile, content: io.readFile(libFile) }; } - if (Error) (Error).stackTraceLimit = 1; + if (Error) (Error).stackTraceLimit = 25; } // TODO: not sure why Utils.evalFile isn't working with this, eventually will concat it like old compiler instead of eval diff --git a/src/server/server.ts b/src/server/server.ts index d9f078ac0eb53..5c2fd7d1c4357 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -7,12 +7,58 @@ namespace ts.server { const readline: NodeJS.ReadLine = require("readline"); const fs: typeof NodeJS.fs = require("fs"); + // TODO: "net" module not defined in local node.d.ts + const net: any = require("net"); + const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false, }); + // Need to write directly to stdout, else rl.write also causes an input "line" event + // See https://github.com/joyent/node/issues/4243 + let writeHost = (data: string) => process.stdout.write(data); + + // Stubs for I/O + const onInput = (input: string) => { return; }; + const onClose = () => { return; }; + + // Use a socket for comms if defined + const tss_debug: string = process.env["TSS_DEBUG"]; + let tcp_port = 0; + if (tss_debug) { + tss_debug.split(" ").forEach( param => { + if (param.indexOf("port=") === 0) { + tcp_port = parseInt(param.substring(5)); + } + }); + if (tcp_port) { + net.createServer( (socket: any) => { + // Called once a connection is made + socket.setEncoding("utf8"); + // Wire up the I/O handers to the socket + writeHost = (data: string) => { + socket.write(data); + return true; + }; + socket.on("data", (data: string) => { + // May get multiple requests in one network read + if (data) { + data.trim().split(/(\r\n)|\n/).forEach(line => onInput(line)); + } + }); + socket.on("end", onClose); + + }).listen(tcp_port); + } + } + if (!tcp_port) { + // If not using tcp, wire up the I/O handler to stdin/stdout + rl.on("line", (input: string) => onInput(input)); + rl.on("close", () => onClose()); + } + class Logger implements ts.server.Logger { fd = -1; seq = 0; diff --git a/src/server/session.ts b/src/server/session.ts index 9b9bf35a4a588..c1bafdf5ba80b 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -792,7 +792,9 @@ namespace ts.server { } private closeClientFile(fileName: string) { - if (!fileName) { return; } + if (!fileName) { + return; + } const file = ts.normalizePath(fileName); this.projectService.closeClientFile(file); } diff --git a/src/services/services.ts b/src/services/services.ts index 0fd4df1990774..c199c39d80827 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2185,7 +2185,6 @@ namespace ts { } return true; } - return false; } @@ -2368,6 +2367,7 @@ namespace ts { // skip open bracket token = scanner.scan(); + let i = 0; // scan until ']' or EOF while (token !== SyntaxKind.CloseBracketToken && token !== SyntaxKind.EndOfFileToken) { diff --git a/tests/baselines/reference/jsFileCompilationSyntaxError.errors.txt b/tests/baselines/reference/jsFileCompilationSyntaxError.errors.txt index 6e55ff30f04fd..d98a1e7e7bbc8 100644 --- a/tests/baselines/reference/jsFileCompilationSyntaxError.errors.txt +++ b/tests/baselines/reference/jsFileCompilationSyntaxError.errors.txt @@ -1,14 +1,11 @@ error TS5055: Cannot write file 'tests/cases/compiler/a.js' because it would overwrite input file. -tests/cases/compiler/a.js(3,6): error TS1223: 'type' tag already specified. !!! error TS5055: Cannot write file 'tests/cases/compiler/a.js' because it would overwrite input file. -==== tests/cases/compiler/a.js (1 errors) ==== +==== tests/cases/compiler/a.js (0 errors) ==== /** * @type {number} * @type {string} - ~~~~ -!!! error TS1223: 'type' tag already specified. */ var v; \ No newline at end of file diff --git a/tests/cases/fourslash/completionInJsDoc.ts b/tests/cases/fourslash/completionInJsDoc.ts index 8051630385649..e5cc7a3d2b609 100644 --- a/tests/cases/fourslash/completionInJsDoc.ts +++ b/tests/cases/fourslash/completionInJsDoc.ts @@ -23,11 +23,8 @@ ////// @pa/*7*/ ////var v7; //// -/////** @param { n/*8*/ } */ +/////** @return { n/*8*/ } */ ////var v8; -//// -/////** @return { n/*9*/ } */ -////var v9; goTo.marker('1'); verify.completionListContains("constructor"); @@ -57,6 +54,3 @@ verify.completionListIsEmpty(); goTo.marker('8'); verify.completionListContains('number'); -goTo.marker('9'); -verify.completionListContains('number'); - diff --git a/tests/cases/fourslash/getJavaScriptCompletions1.ts b/tests/cases/fourslash/getJavaScriptCompletions1.ts new file mode 100644 index 0000000000000..6d8f36aac1b51 --- /dev/null +++ b/tests/cases/fourslash/getJavaScriptCompletions1.ts @@ -0,0 +1,10 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: Foo.js +/////** @type {number} */ +////var v; +////v./**/ + +goTo.marker(); +verify.completionListContains("toExponential", /*displayText:*/ undefined, /*documentation*/ undefined, "method"); diff --git a/tests/cases/fourslash/getJavaScriptCompletions10.ts b/tests/cases/fourslash/getJavaScriptCompletions10.ts new file mode 100644 index 0000000000000..5fbd91e89881a --- /dev/null +++ b/tests/cases/fourslash/getJavaScriptCompletions10.ts @@ -0,0 +1,11 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: Foo.js +/////** +//// * @type {function(this:number)} +//// */ +////function f() { this./**/ } + +goTo.marker(); +verify.completionListContains("toExponential", /*displayText:*/ undefined, /*documentation*/ undefined, "method"); \ No newline at end of file diff --git a/tests/cases/fourslash/getJavaScriptCompletions11.ts b/tests/cases/fourslash/getJavaScriptCompletions11.ts new file mode 100644 index 0000000000000..f12f53fa960fe --- /dev/null +++ b/tests/cases/fourslash/getJavaScriptCompletions11.ts @@ -0,0 +1,11 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: Foo.js +/////** @type {number|string} */ +////var v; +////v./**/ + +goTo.marker(); +verify.completionListContains("toExponential", /*displayText:*/ undefined, /*documentation*/ undefined, "method"); +verify.completionListContains("charCodeAt", /*displayText:*/ undefined, /*documentation*/ undefined, "method"); \ No newline at end of file diff --git a/tests/cases/fourslash/getJavaScriptCompletions12.ts b/tests/cases/fourslash/getJavaScriptCompletions12.ts new file mode 100644 index 0000000000000..99b1482885440 --- /dev/null +++ b/tests/cases/fourslash/getJavaScriptCompletions12.ts @@ -0,0 +1,36 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: Foo.js +/////** +//// * @param {number} input +//// * @param {string} currency +//// * @returns {number} +//// */ +////var convert = function(input, currency) { +//// switch(currency./*1*/) { +//// case "USD": +//// input./*2*/; +//// case "EUR": +//// return "" + rateToUsd.EUR; +//// case "CNY": +//// return {} + rateToUsd.CNY; +//// } +////} +////convert(1, "")./*3*/ +/////** +//// * @param {number} x +//// */ +////var test1 = function(x) { return x./*4*/ }, test2 = function(a) { return a./*5*/ }; + + +goTo.marker("1"); +verify.completionListContains("charCodeAt", /*displayText:*/ undefined, /*documentation*/ undefined, "method"); +goTo.marker("2"); +verify.completionListContains("toExponential", /*displayText:*/ undefined, /*documentation*/ undefined, "method"); +goTo.marker("3"); +verify.completionListContains("toExponential", /*displayText:*/ undefined, /*documentation*/ undefined, "method"); +goTo.marker("4"); +verify.completionListContains("toExponential", /*displayText:*/ undefined, /*documentation*/ undefined, "method"); +goTo.marker("5"); +verify.completionListContains("test1", /*displayText:*/ undefined, /*documentation*/ undefined, "warning"); \ No newline at end of file diff --git a/tests/cases/fourslash/getJavaScriptCompletions13.ts b/tests/cases/fourslash/getJavaScriptCompletions13.ts new file mode 100644 index 0000000000000..438c73e304c04 --- /dev/null +++ b/tests/cases/fourslash/getJavaScriptCompletions13.ts @@ -0,0 +1,26 @@ +/// +// @allowNonTsExtensions: true + +// @Filename: file1.js + +////var file1Identifier = 1; +////interface Foo { FooProp: number }; + +// @Filename: file2.js + +////var file2Identifier1 = 2; +////var file2Identifier2 = 2; +/////*1*/ +////file2Identifier2./*2*/ + +goTo.marker("1"); +verify.completionListContains("file2Identifier1"); +verify.completionListContains("file2Identifier2"); +verify.completionListContains("file1Identifier"); +verify.not.completionListContains("FooProp"); + +goTo.marker("2"); +verify.completionListContains("file2Identifier1"); +verify.completionListContains("file2Identifier2"); +verify.not.completionListContains("file1Identifier") +verify.not.completionListContains("FooProp"); \ No newline at end of file diff --git a/tests/cases/fourslash/getJavaScriptCompletions14.ts b/tests/cases/fourslash/getJavaScriptCompletions14.ts new file mode 100644 index 0000000000000..52a23065a5e2e --- /dev/null +++ b/tests/cases/fourslash/getJavaScriptCompletions14.ts @@ -0,0 +1,13 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: file1.js +/////// +////interface Number { +//// toExponential(fractionDigits?: number): string; +////} +////var x = 1; +////x./*1*/ + +goTo.marker("1"); +verify.completionListContains("toExponential", /*displayText:*/ undefined, /*documentation*/ undefined, "method"); diff --git a/tests/cases/fourslash/getJavaScriptCompletions15.ts b/tests/cases/fourslash/getJavaScriptCompletions15.ts new file mode 100644 index 0000000000000..fbcbc2d4692a9 --- /dev/null +++ b/tests/cases/fourslash/getJavaScriptCompletions15.ts @@ -0,0 +1,29 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: refFile1.ts +//// export var V = 1; + +// @Filename: refFile2.ts +//// export var V = "123" + +// @Filename: refFile3.ts +//// export var V = "123" + +// @Filename: main.js +//// import ref1 = require("refFile1"); +//// var ref2 = require("refFile2"); +//// ref1.V./*1*/; +//// ref2.V./*2*/; +//// var v = { x: require("refFile3") }; +//// v.x./*3*/; +//// v.x.V./*4*/; + +goTo.marker("1"); +verify.completionListContains("toExponential"); +goTo.marker("2"); +verify.completionListContains("toLowerCase"); +goTo.marker("3"); +verify.completionListContains("V"); +goTo.marker("4"); +verify.completionListContains("toLowerCase"); diff --git a/tests/cases/fourslash/getJavaScriptCompletions2.ts b/tests/cases/fourslash/getJavaScriptCompletions2.ts new file mode 100644 index 0000000000000..0cb2de046a668 --- /dev/null +++ b/tests/cases/fourslash/getJavaScriptCompletions2.ts @@ -0,0 +1,10 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: Foo.js +/////** @type {(number|string)} */ +////var v; +////v./**/ + +goTo.marker(); +verify.completionListContains("valueOf", /*displayText:*/ undefined, /*documentation*/ undefined, "method"); \ No newline at end of file diff --git a/tests/cases/fourslash/getJavaScriptCompletions3.ts b/tests/cases/fourslash/getJavaScriptCompletions3.ts new file mode 100644 index 0000000000000..13a6f1673e6b3 --- /dev/null +++ b/tests/cases/fourslash/getJavaScriptCompletions3.ts @@ -0,0 +1,10 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: Foo.js +/////** @type {Array.} */ +////var v; +////v./**/ + +goTo.marker(); +verify.completionListContains("concat", /*displayText:*/ undefined, /*documentation*/ undefined, "method"); \ No newline at end of file diff --git a/tests/cases/fourslash/getJavaScriptCompletions4.ts b/tests/cases/fourslash/getJavaScriptCompletions4.ts new file mode 100644 index 0000000000000..3719b8e1258cb --- /dev/null +++ b/tests/cases/fourslash/getJavaScriptCompletions4.ts @@ -0,0 +1,10 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: Foo.js +/////** @return {number} */ +////function foo(a,b) { } +////foo(1,2)./**/ + +goTo.marker(); +verify.completionListContains("toExponential", /*displayText:*/ undefined, /*documentation*/ undefined, "method"); \ No newline at end of file diff --git a/tests/cases/fourslash/getJavaScriptCompletions5.ts b/tests/cases/fourslash/getJavaScriptCompletions5.ts new file mode 100644 index 0000000000000..348feef94c329 --- /dev/null +++ b/tests/cases/fourslash/getJavaScriptCompletions5.ts @@ -0,0 +1,15 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: Foo.js +//// /** +//// * @template T +//// * @param {T} a +//// * @return {T} */ +//// function foo(a) { } +//// let x = /*1*/foo; +//// foo(1)./**/ + +goTo.marker('1'); +goTo.marker(); +verify.completionListContains("toExponential", /*displayText:*/ undefined, /*documentation*/ undefined, "method"); diff --git a/tests/cases/fourslash/getJavaScriptCompletions6.ts b/tests/cases/fourslash/getJavaScriptCompletions6.ts new file mode 100644 index 0000000000000..9f9a42b5762c2 --- /dev/null +++ b/tests/cases/fourslash/getJavaScriptCompletions6.ts @@ -0,0 +1,13 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: Foo.js +/////** +//// * @param {...number} a +//// */ +////function foo(a) { +//// a./**/ +////} + +goTo.marker(); +verify.completionListContains("concat", /*displayText:*/ undefined, /*documentation*/ undefined, "method"); \ No newline at end of file diff --git a/tests/cases/fourslash/getJavaScriptCompletions7.ts b/tests/cases/fourslash/getJavaScriptCompletions7.ts new file mode 100644 index 0000000000000..d8b01cfb757c2 --- /dev/null +++ b/tests/cases/fourslash/getJavaScriptCompletions7.ts @@ -0,0 +1,13 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: Foo.js +/////** +//// * @param {...number} a +//// */ +////function foo(a) { +//// a[0]./**/ +////} + +goTo.marker(); +verify.completionListContains("toExponential", /*displayText:*/ undefined, /*documentation*/ undefined, "method"); \ No newline at end of file diff --git a/tests/cases/fourslash/getJavaScriptCompletions8.ts b/tests/cases/fourslash/getJavaScriptCompletions8.ts new file mode 100644 index 0000000000000..418aae676bee5 --- /dev/null +++ b/tests/cases/fourslash/getJavaScriptCompletions8.ts @@ -0,0 +1,12 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: Foo.js +/////** +//// * @type {function(): number} +//// */ +////var v; +////v()./**/ + +goTo.marker(); +verify.completionListContains("toExponential", /*displayText:*/ undefined, /*documentation*/ undefined, "method"); \ No newline at end of file diff --git a/tests/cases/fourslash/getJavaScriptCompletions9.ts b/tests/cases/fourslash/getJavaScriptCompletions9.ts new file mode 100644 index 0000000000000..1619a2ca9fc49 --- /dev/null +++ b/tests/cases/fourslash/getJavaScriptCompletions9.ts @@ -0,0 +1,12 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: Foo.js +/////** +//// * @type {function(new:number)} +//// */ +////var v; +////new v()./**/ + +goTo.marker(); +verify.completionListContains("toExponential", /*displayText:*/ undefined, /*documentation*/ undefined, "method"); \ No newline at end of file diff --git a/tests/cases/fourslash/getJavaScriptQuickInfo1.ts b/tests/cases/fourslash/getJavaScriptQuickInfo1.ts new file mode 100644 index 0000000000000..71072f1699aa2 --- /dev/null +++ b/tests/cases/fourslash/getJavaScriptQuickInfo1.ts @@ -0,0 +1,9 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: Foo.js +/////** @type {function(new:string,number)} */ +////var /**/v; + +goTo.marker(); +verify.quickInfoIs('var v: new (p1: number) => string'); \ No newline at end of file diff --git a/tests/cases/fourslash/getJavaScriptQuickInfo2.ts b/tests/cases/fourslash/getJavaScriptQuickInfo2.ts new file mode 100644 index 0000000000000..ce347a62b2727 --- /dev/null +++ b/tests/cases/fourslash/getJavaScriptQuickInfo2.ts @@ -0,0 +1,9 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: Foo.js +/////** @param {number} [a] */ +////function /**/f(a) { } + +goTo.marker(); +verify.quickInfoIs('function f(a?: number): void'); \ No newline at end of file diff --git a/tests/cases/fourslash/getJavaScriptQuickInfo3.ts b/tests/cases/fourslash/getJavaScriptQuickInfo3.ts new file mode 100644 index 0000000000000..2e094bd21f6f2 --- /dev/null +++ b/tests/cases/fourslash/getJavaScriptQuickInfo3.ts @@ -0,0 +1,9 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: Foo.js +/////** @param {number[]} [a] */ +////function /**/f(a) { } + +goTo.marker(); +verify.quickInfoIs('function f(a?: number[]): void'); \ No newline at end of file diff --git a/tests/cases/fourslash/getJavaScriptQuickInfo4.ts b/tests/cases/fourslash/getJavaScriptQuickInfo4.ts new file mode 100644 index 0000000000000..34ee10d97b747 --- /dev/null +++ b/tests/cases/fourslash/getJavaScriptQuickInfo4.ts @@ -0,0 +1,9 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: Foo.js +/////** @param {[number,string]} [a] */ +////function /**/f(a) { } + +goTo.marker(); +verify.quickInfoIs('function f(a?: [number, string]): void'); \ No newline at end of file diff --git a/tests/cases/fourslash/getJavaScriptQuickInfo5.ts b/tests/cases/fourslash/getJavaScriptQuickInfo5.ts new file mode 100644 index 0000000000000..004af0811b7ac --- /dev/null +++ b/tests/cases/fourslash/getJavaScriptQuickInfo5.ts @@ -0,0 +1,9 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: Foo.js +/////** @param {{b:number}} [a] */ +////function /**/f(a) { } + +goTo.marker(); +verify.quickInfoIs('function f(a?: {\n b: number;\n}): void'); \ No newline at end of file diff --git a/tests/cases/fourslash/getJavaScriptQuickInfo6.ts b/tests/cases/fourslash/getJavaScriptQuickInfo6.ts new file mode 100644 index 0000000000000..3aebd19deda12 --- /dev/null +++ b/tests/cases/fourslash/getJavaScriptQuickInfo6.ts @@ -0,0 +1,9 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: Foo.js +/////** @type {function(this:number)} */ +////function f() { /**/this } + +goTo.marker(); +verify.quickInfoIs('number'); \ No newline at end of file diff --git a/tests/cases/fourslash/getJavaScriptQuickInfo7.ts b/tests/cases/fourslash/getJavaScriptQuickInfo7.ts new file mode 100644 index 0000000000000..282f5aeabfd3d --- /dev/null +++ b/tests/cases/fourslash/getJavaScriptQuickInfo7.ts @@ -0,0 +1,10 @@ +/// + +// @allowNonTsExtensions: true +// @Filename: Foo.js +//// function f(a,b) { } +//// /** @type {f} */ +//// var v/**/ + +goTo.marker(); +verify.quickInfoIs('var v: (a: any, b: any) => void'); \ No newline at end of file diff --git a/tests/cases/fourslash/javaScriptModulesError1.ts b/tests/cases/fourslash/javaScriptModulesError1.ts new file mode 100644 index 0000000000000..f13b25a182efb --- /dev/null +++ b/tests/cases/fourslash/javaScriptModulesError1.ts @@ -0,0 +1,12 @@ +/// + +// Error: Having more function parameters than entries in the dependency array + +// @allowNonTsExtensions: true +// @Filename: Foo.js +//// define('mod1', ['a'], /**/function(a, b) { +//// +//// }); + +// TODO: what should happen? +goTo.marker(); \ No newline at end of file diff --git a/tests/cases/unittests/jsDocParsing.ts b/tests/cases/unittests/jsDocParsing.ts index ebba6e8a1a648..9988383b467da 100644 --- a/tests/cases/unittests/jsDocParsing.ts +++ b/tests/cases/unittests/jsDocParsing.ts @@ -1,4 +1,5 @@ /// +/// /// /// @@ -985,15 +986,29 @@ module ts { describe("DocComments", () => { function parsesCorrectly(content: string, expected: string) { let comment = parseIsolatedJSDocComment(content); - Debug.assert(comment && comment.diagnostics.length === 0); + if (!comment) { + Debug.fail('Comment failed to parse entirely'); + } + if (comment.diagnostics.length > 0) { + Debug.fail('Comment has at least one diagnostic: ' + comment.diagnostics[0].messageText); + } let result = JSON.stringify(comment.jsDocComment, (k, v) => { return v && v.pos !== undefined ? JSON.parse(Utils.sourceFileToJSON(v)) : v; - }, " "); + }, 4); - assert.equal(result, expected); + if (result !== expected) { + // Turn on a human-readable diff + if (typeof require !== 'undefined') { + require('chai').config.showDiff = true; + chai.expect(JSON.parse(result)).equal(JSON.parse(expected)); + } + else { + assert.equal(result, expected); + } + } } function parsesIncorrectly(content: string) { @@ -1577,7 +1592,7 @@ module ts { "0": { "kind": "JSDocParameterTag", "pos": 8, - "end": 30, + "end": 31, "atToken": { "kind": "AtToken", "pos": 8, @@ -1609,7 +1624,7 @@ module ts { }, "length": 1, "pos": 8, - "end": 30 + "end": 31 } }`); }); @@ -1627,7 +1642,7 @@ module ts { "0": { "kind": "JSDocParameterTag", "pos": 8, - "end": 31, + "end": 36, "atToken": { "kind": "AtToken", "pos": 8, @@ -1659,7 +1674,7 @@ module ts { }, "length": 1, "pos": 8, - "end": 31 + "end": 36 } }`); }); @@ -2113,7 +2128,7 @@ module ts { "0": { "kind": "JSDocTemplateTag", "pos": 8, - "end": 24, + "end": 23, "atToken": { "kind": "AtToken", "pos": 8, @@ -2150,12 +2165,12 @@ module ts { }, "length": 2, "pos": 17, - "end": 24 + "end": 23 } }, "length": 1, "pos": 8, - "end": 24 + "end": 23 } }`); }); From 68f11b44fa3e573d1d33456789b5e138103c8e38 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Wed, 6 Jan 2016 14:20:04 -0800 Subject: [PATCH 02/14] Remove unrelated changes --- src/compiler/checker.ts | 57 ++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a8e2d2b4055d4..03a57bee46844 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -164,6 +164,8 @@ namespace ts { let getGlobalPromiseConstructorLikeType: () => ObjectType; let getGlobalThenableType: () => ObjectType; + let jsxElementClassType: Type; + let deferredNodes: Node[]; const tupleTypes: Map = {}; @@ -7502,6 +7504,7 @@ namespace ts { if (isContextSensitive(func)) { const contextualSignature = getContextualSignature(func); if (contextualSignature) { + const funcHasRestParameters = hasRestParameter(func); const len = func.parameters.length - (funcHasRestParameters ? 1 : 0); const indexOfParameter = indexOf(func.parameters, parameter); @@ -8605,7 +8608,10 @@ namespace ts { } function getJsxGlobalElementClassType(): Type { - return getExportedTypeFromNamespace(JsxNames.JSX, JsxNames.ElementClass); + if (!jsxElementClassType) { + jsxElementClassType = getExportedTypeFromNamespace(JsxNames.JSX, JsxNames.ElementClass); + } + return jsxElementClassType; } /// Returns all the properties of the Jsx.IntrinsicElements interface @@ -9407,33 +9413,32 @@ namespace ts { */ function getEffectiveDecoratorFirstArgumentType(node: Node): Type { // The first argument to a decorator is its `target`. - switch (node.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - // For a class decorator, the `target` is the type of the class (e.g. the - // "static" or "constructor" side of the class) + if (node.kind === SyntaxKind.ClassDeclaration) { + // For a class decorator, the `target` is the type of the class (e.g. the + // "static" or "constructor" side of the class) + const classSymbol = getSymbolOfNode(node); + return getTypeOfSymbol(classSymbol); + } + + if (node.kind === SyntaxKind.Parameter) { + // For a parameter decorator, the `target` is the parent type of the + // parameter's containing method. + node = node.parent; + if (node.kind === SyntaxKind.Constructor) { const classSymbol = getSymbolOfNode(node); return getTypeOfSymbol(classSymbol); + } + } - case SyntaxKind.Parameter: - // For a parameter decorator, the `target` is the parent type of the - // parameter's containing method. - node = node.parent; - if (node.kind === SyntaxKind.Constructor) { - const classSymbol = getSymbolOfNode(node); - return getTypeOfSymbol(classSymbol); - } - - // fall-through - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - // For a property or method decorator, the `target` is the - // "static"-side type of the parent of the member if the member is - // declared "static"; otherwise, it is the "instance"-side type of the - // parent of the member. - return getParentTypeOfClassElement(node); + if (node.kind === SyntaxKind.PropertyDeclaration || + node.kind === SyntaxKind.MethodDeclaration || + node.kind === SyntaxKind.GetAccessor || + node.kind === SyntaxKind.SetAccessor) { + // For a property or method decorator, the `target` is the + // "static"-side type of the parent of the member if the member is + // declared "static"; otherwise, it is the "instance"-side type of the + // parent of the member. + return getParentTypeOfClassElement(node); } Debug.fail("Unsupported decorator target."); @@ -10243,11 +10248,11 @@ namespace ts { } function getReturnTypeFromBody(func: FunctionLikeDeclaration, contextualMapper?: TypeMapper): Type { + const contextualSignature = getContextualSignatureForFunctionLikeDeclaration(func); if (!func.body) { return unknownType; } - const contextualSignature = getContextualSignatureForFunctionLikeDeclaration(func); const isAsync = isAsyncFunctionLike(func); let type: Type; if (func.body.kind !== SyntaxKind.Block) { From c006634cb74f674d5ad61aaf8ad6f7111224460b Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Thu, 7 Jan 2016 16:14:53 -0800 Subject: [PATCH 03/14] JSDoc identifiers must start with an identifier start --- src/compiler/scanner.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index d03ab77569c93..96c59c8b1bd5e 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -1725,9 +1725,12 @@ namespace ts { else if (isWhiteSpace(ch)) { // Keep going } - else { + else if (isIdentifierStart(ch, ScriptTarget.Latest)) { identifierStarted = true; } + else { + return pos += 1, token = SyntaxKind.Unknown; + } } pos += 1; } From b1711e3633d9035e3737bec930e8a33ea6325466 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Thu, 7 Jan 2016 17:33:46 -0800 Subject: [PATCH 04/14] scanJsIdentifier -> parseJSDocIdentifier --- src/compiler/parser.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 78a3b567ef3ea..e86e05068dbac 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -5999,12 +5999,11 @@ namespace ts { atToken.end = scanner.getTextPos(); nextJSDocToken(); - const tagName = scanJsDocIdentifier(); + const tagName = parseJSDocIdentifier(); if (!tagName) { return; } - nextJSDocToken(); const tag = handleTag(atToken, tagName) || handleUnknownTag(atToken, tagName); addTag(tag); } @@ -6063,8 +6062,7 @@ namespace ts { let isBracketed: boolean; // Looking for something like '[foo]' or 'foo' if (parseOptionalToken(SyntaxKind.OpenBracketToken)) { - name = scanJsDocIdentifier(); - nextJSDocToken(); + name = parseJSDocIdentifier(); isBracketed = true; // May have an optional default, e.g. '[foo = 42]' @@ -6075,8 +6073,7 @@ namespace ts { parseExpected(SyntaxKind.CloseBracketToken); } else if (token === SyntaxKind.Identifier) { - name = scanJsDocIdentifier(); - nextJSDocToken(); + name = parseJSDocIdentifier(); } if (!name) { @@ -6140,7 +6137,7 @@ namespace ts { typeParameters.pos = scanner.getStartPos(); while (true) { - const name = scanJsDocIdentifier(); + const name = parseJSDocIdentifier(); if (!name) { parseErrorAtPosition(scanner.getStartPos(), 0, Diagnostics.Identifier_expected); return undefined; @@ -6148,7 +6145,6 @@ namespace ts { const typeParameter = createNode(SyntaxKind.TypeParameter, name.pos); typeParameter.name = name; - nextJSDocToken(); finishNode(typeParameter); typeParameters.push(typeParameter); @@ -6174,7 +6170,7 @@ namespace ts { return token = scanner.scanJSDocToken(); } - function scanJsDocIdentifier(): Identifier { + function parseJSDocIdentifier(): Identifier { if (token !== SyntaxKind.Identifier) { parseErrorAtCurrentToken(Diagnostics.Identifier_expected); return undefined; @@ -6184,7 +6180,10 @@ namespace ts { const end = scanner.getTextPos(); const result = createNode(SyntaxKind.Identifier, pos); result.text = content.substring(pos, end); - return finishNode(result, end); + finishNode(result, end); + + nextJSDocToken(); + return result; } } } From a3126fd6be67802c6413899095d0193abed6c97d Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Fri, 8 Jan 2016 17:49:22 -0800 Subject: [PATCH 05/14] Simplify JSDoc scanner loop --- src/compiler/scanner.ts | 84 ++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 48 deletions(-) diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 96c59c8b1bd5e..b53b1b5c5c13e 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -1672,11 +1672,16 @@ namespace ts { } function scanJSDocToken(): SyntaxKind { + if (pos >= end) { + return token = SyntaxKind.EndOfFileToken; + } + startPos = pos; // Eat leading whitespace + let ch = text.charCodeAt(pos); while (pos < end) { - const ch = text.charCodeAt(pos); + ch = text.charCodeAt(pos); if (isWhiteSpace(ch)) { pos++; } @@ -1686,55 +1691,38 @@ namespace ts { } tokenPos = pos; - let identifierStarted = false; - while (pos < end) { - const ch = text.charCodeAt(pos); - if (identifierStarted) { - if (!isIdentifierPart(ch, ScriptTarget.Latest)) { - return token = SyntaxKind.Identifier; - } - } - else { - if (ch === CharacterCodes.at) { - return pos += 1, token = SyntaxKind.AtToken; - } - else if (isLineBreak(ch)) { - return pos += 1, token = SyntaxKind.NewLineTrivia; - } - else if (ch === CharacterCodes.asterisk) { - return pos += 1, token = SyntaxKind.AsteriskToken; - } - else if (ch === CharacterCodes.openBrace) { - return pos += 1, token = SyntaxKind.OpenBraceToken; - } - else if (ch === CharacterCodes.closeBrace) { - return pos += 1, token = SyntaxKind.CloseBraceToken; - } - else if (ch === CharacterCodes.openBracket) { - return pos += 1, token = SyntaxKind.OpenBracketToken; - } - else if (ch === CharacterCodes.closeBracket) { - return pos += 1, token = SyntaxKind.CloseBracketToken; - } - else if (ch === CharacterCodes.equals) { - return pos += 1, token = SyntaxKind.EqualsToken; - } - else if (ch === CharacterCodes.comma) { - return pos += 1, token = SyntaxKind.CommaToken; - } - else if (isWhiteSpace(ch)) { - // Keep going - } - else if (isIdentifierStart(ch, ScriptTarget.Latest)) { - identifierStarted = true; - } - else { - return pos += 1, token = SyntaxKind.Unknown; - } + switch (ch) { + case CharacterCodes.at: + return pos += 1, token = SyntaxKind.AtToken; + case CharacterCodes.lineFeed: + case CharacterCodes.carriageReturn: + return pos += 1, token = SyntaxKind.NewLineTrivia; + case CharacterCodes.asterisk: + return pos += 1, token = SyntaxKind.AsteriskToken; + case CharacterCodes.openBrace: + return pos += 1, token = SyntaxKind.OpenBraceToken; + case CharacterCodes.closeBrace: + return pos += 1, token = SyntaxKind.CloseBraceToken; + case CharacterCodes.openBracket: + return pos += 1, token = SyntaxKind.OpenBracketToken; + case CharacterCodes.closeBracket: + return pos += 1, token = SyntaxKind.CloseBracketToken; + case CharacterCodes.equals: + return pos += 1, token = SyntaxKind.EqualsToken; + case CharacterCodes.comma: + return pos += 1, token = SyntaxKind.CommaToken; + } + + if (isIdentifierStart(ch, ScriptTarget.Latest)) { + pos++; + while (isIdentifierPart(text.charCodeAt(pos), ScriptTarget.Latest) && pos < end) { + pos++; } - pos += 1; + return token = SyntaxKind.Identifier; + } + else { + return pos += 1, token = SyntaxKind.Unknown; } - return token = SyntaxKind.EndOfFileToken; } function speculationHelper(callback: () => T, isLookahead: boolean): T { From 4fe33732cf9639db3ae0fe039964f49590afd457 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Mon, 11 Jan 2016 13:35:33 -0800 Subject: [PATCH 06/14] Tidy up unused comments / code --- src/compiler/binder.ts | 6 ++---- src/compiler/checker.ts | 6 +----- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 6b63cbdb5dfac..a9aed6a522182 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -888,7 +888,7 @@ namespace ts { } } - function bindFunctionOrConstructorTypeOrJSDocFunctionType(node: SignatureDeclaration): void { + function bindFunctionOrConstructorType(node: SignatureDeclaration): void { // For a given function symbol "<...>(...) => T" we want to generate a symbol identical // to the one we would get for: { <...>(...): T } // @@ -1274,7 +1274,7 @@ namespace ts { case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: case SyntaxKind.JSDocFunctionType: - return bindFunctionOrConstructorTypeOrJSDocFunctionType(node); + return bindFunctionOrConstructorType(node); case SyntaxKind.TypeLiteral: case SyntaxKind.JSDocRecordType: return bindAnonymousDeclaration(node, SymbolFlags.TypeLiteral, "__type"); @@ -1288,8 +1288,6 @@ namespace ts { case SyntaxKind.CallExpression: if (isInJavaScriptFile(node)) { - // We're only inspecting call expressions to detect CommonJS modules, so we can skip - // this check if we've already seen the module indicator bindCallExpression(node); } break; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6d7a9c5ffa7dc..229d66ec4fdfc 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3924,7 +3924,7 @@ namespace ts { return result; } - function isOptionalParameter(node: ParameterDeclaration, skipSignatureCheck?: boolean) { + function isOptionalParameter(node: ParameterDeclaration) { if (node.parserContextFlags & ParserContextFlags.JavaScriptFile) { if (node.type && node.type.kind === SyntaxKind.JSDocOptionalType) { return true; @@ -3947,10 +3947,6 @@ namespace ts { } if (node.initializer) { - if (skipSignatureCheck) { - return true; - } - const signatureDeclaration = node.parent; const signature = getSignatureFromDeclaration(signatureDeclaration); const parameterIndex = ts.indexOf(signatureDeclaration.parameters, node); From 120fa190d22fda462df989ce04268c67c0523305 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Tue, 12 Jan 2016 15:30:19 -0800 Subject: [PATCH 07/14] Remove duplicated functions --- src/compiler/checker.ts | 76 +++++++---------------------------------- src/compiler/types.ts | 2 ++ 2 files changed, 15 insertions(+), 63 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 229d66ec4fdfc..99173b816f016 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4727,23 +4727,6 @@ namespace ts { return links.resolvedType; } - function getTypeFromJSDocFunctionType(node: JSDocFunctionType): Type { - Debug.assert(!!node.symbol); - const links = getNodeLinks(node); - if (!links.resolvedType) { - links.resolvedType = createObjectType(TypeFlags.Anonymous, node.symbol); - } - return links.resolvedType; - } - - function getTypeFromJSDocRecordType(node: JSDocRecordType): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - links.resolvedType = createObjectType(TypeFlags.Anonymous, node.symbol); - } - return links.resolvedType; - } - function getTypeFromJSDocVariadicType(node: JSDocVariadicType): Type { const links = getNodeLinks(node); if (!links.resolvedType) { @@ -4753,27 +4736,6 @@ namespace ts { return links.resolvedType; } - function getTypeFromJSDocTypeReference(node: JSDocTypeReference): Type { - return getTypeFromTypeReference(node); - } - - function getTypeFromJSDocArrayType(node: JSDocArrayType): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - links.resolvedType = createArrayType(getTypeFromTypeNode(node.elementType)); - } - return links.resolvedType; - } - - function getTypeFromJSDocUnionType(node: JSDocUnionType): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - const types = map(node.types, getTypeFromTypeNode); - links.resolvedType = getUnionType(types, /*noSubtypeReduction*/ true); - } - return links.resolvedType; - } - function getTypeFromJSDocTupleType(node: JSDocTupleType): Type { const links = getNodeLinks(node); if (!links.resolvedType) { @@ -4826,6 +4788,8 @@ namespace ts { function getTypeFromTypeNode(node: TypeNode): Type { switch (node.kind) { case SyntaxKind.AnyKeyword: + case SyntaxKind.JSDocAllType: + case SyntaxKind.JSDocUnknownType: return anyType; case SyntaxKind.StringKeyword: return stringType; @@ -4842,6 +4806,7 @@ namespace ts { case SyntaxKind.StringLiteralType: return getTypeFromStringLiteralTypeNode(node); case SyntaxKind.TypeReference: + case SyntaxKind.JSDocTypeReference: return getTypeFromTypeReference(node); case SyntaxKind.TypePredicate: return getTypeFromPredicateTypeNode(node); @@ -4850,18 +4815,27 @@ namespace ts { case SyntaxKind.TypeQuery: return getTypeFromTypeQueryNode(node); case SyntaxKind.ArrayType: + case SyntaxKind.JSDocArrayType: return getTypeFromArrayTypeNode(node); case SyntaxKind.TupleType: return getTypeFromTupleTypeNode(node); case SyntaxKind.UnionType: + case SyntaxKind.JSDocUnionType: return getTypeFromUnionTypeNode(node); case SyntaxKind.IntersectionType: return getTypeFromIntersectionTypeNode(node); case SyntaxKind.ParenthesizedType: - return getTypeFromTypeNode((node).type); + case SyntaxKind.JSDocNullableType: + case SyntaxKind.JSDocNonNullableType: + case SyntaxKind.JSDocConstructorType: + case SyntaxKind.JSDocThisType: + case SyntaxKind.JSDocOptionalType: + return getTypeFromTypeNode((node).type); case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: case SyntaxKind.TypeLiteral: + case SyntaxKind.JSDocFunctionType: + case SyntaxKind.JSDocRecordType: return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); // This function assumes that an identifier or qualified name is a type expression // Callers should first ensure this by calling isTypeNode @@ -4869,34 +4843,10 @@ namespace ts { case SyntaxKind.QualifiedName: const symbol = getSymbolAtLocation(node); return symbol && getDeclaredTypeOfSymbol(symbol); - case SyntaxKind.JSDocAllType: - return anyType; - case SyntaxKind.JSDocUnknownType: - return unknownType; - case SyntaxKind.JSDocArrayType: - return getTypeFromJSDocArrayType(node); case SyntaxKind.JSDocTupleType: return getTypeFromJSDocTupleType(node); - case SyntaxKind.JSDocUnionType: - return getTypeFromJSDocUnionType(node); - case SyntaxKind.JSDocNullableType: - return getTypeFromTypeNode((node).type); - case SyntaxKind.JSDocNonNullableType: - return getTypeFromTypeNode((node).type); - case SyntaxKind.JSDocTypeReference: - return getTypeFromJSDocTypeReference(node); - case SyntaxKind.JSDocOptionalType: - return getTypeFromTypeNode((node).type); - case SyntaxKind.JSDocFunctionType: - return getTypeFromJSDocFunctionType(node); case SyntaxKind.JSDocVariadicType: return getTypeFromJSDocVariadicType(node); - case SyntaxKind.JSDocConstructorType: - return getTypeFromTypeNode((node).type); - case SyntaxKind.JSDocRecordType: - return getTypeFromJSDocRecordType(node); - case SyntaxKind.JSDocThisType: - return getTypeFromTypeNode((node).type); default: return unknownType; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 6e0e8018baa63..7d76562c0f931 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1475,6 +1475,8 @@ namespace ts { type: JSDocType; } + export type JSDocTypeReferencingNode = JSDocThisType | JSDocConstructorType | JSDocVariadicType | JSDocOptionalType | JSDocNullableType | JSDocNonNullableType; + // @kind(SyntaxKind.JSDocRecordMember) export interface JSDocRecordMember extends PropertySignature { name: Identifier | LiteralExpression; From 6303d8c723a772b6f12459c561fb88e83232a26a Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Fri, 22 Jan 2016 15:13:54 -0800 Subject: [PATCH 08/14] Remove incorrect changes from server.ts --- src/server/server.ts | 46 -------------------------------------------- 1 file changed, 46 deletions(-) diff --git a/src/server/server.ts b/src/server/server.ts index 5c2fd7d1c4357..d9f078ac0eb53 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -7,58 +7,12 @@ namespace ts.server { const readline: NodeJS.ReadLine = require("readline"); const fs: typeof NodeJS.fs = require("fs"); - // TODO: "net" module not defined in local node.d.ts - const net: any = require("net"); - const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false, }); - // Need to write directly to stdout, else rl.write also causes an input "line" event - // See https://github.com/joyent/node/issues/4243 - let writeHost = (data: string) => process.stdout.write(data); - - // Stubs for I/O - const onInput = (input: string) => { return; }; - const onClose = () => { return; }; - - // Use a socket for comms if defined - const tss_debug: string = process.env["TSS_DEBUG"]; - let tcp_port = 0; - if (tss_debug) { - tss_debug.split(" ").forEach( param => { - if (param.indexOf("port=") === 0) { - tcp_port = parseInt(param.substring(5)); - } - }); - if (tcp_port) { - net.createServer( (socket: any) => { - // Called once a connection is made - socket.setEncoding("utf8"); - // Wire up the I/O handers to the socket - writeHost = (data: string) => { - socket.write(data); - return true; - }; - socket.on("data", (data: string) => { - // May get multiple requests in one network read - if (data) { - data.trim().split(/(\r\n)|\n/).forEach(line => onInput(line)); - } - }); - socket.on("end", onClose); - - }).listen(tcp_port); - } - } - if (!tcp_port) { - // If not using tcp, wire up the I/O handler to stdin/stdout - rl.on("line", (input: string) => onInput(input)); - rl.on("close", () => onClose()); - } - class Logger implements ts.server.Logger { fd = -1; seq = 0; From af4fb093f762830fdd8170959774c144b1a638b5 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Fri, 22 Jan 2016 15:14:29 -0800 Subject: [PATCH 09/14] Rename getTypeParametersFromJSDocTemplate --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9c22ca3362022..26b80b1aeee38 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3958,7 +3958,7 @@ namespace ts { return getIndexTypeOfStructuredType(getApparentType(type), kind); } - function getTypeParametersFromSignatureDeclaration(declaration: SignatureDeclaration): TypeParameter[] { + function getTypeParametersFromJSDocTemplate(declaration: SignatureDeclaration): TypeParameter[] { if (declaration.parserContextFlags & ParserContextFlags.JavaScriptFile) { const templateTag = getJSDocTemplateTag(declaration); if (templateTag) { @@ -4055,7 +4055,7 @@ namespace ts { : undefined; const typeParameters = classType ? classType.localTypeParameters : declaration.typeParameters ? getTypeParametersFromDeclaration(declaration.typeParameters) : - getTypeParametersFromSignatureDeclaration(declaration); + getTypeParametersFromJSDocTemplate(declaration); const parameters: Symbol[] = []; let hasStringLiterals = false; let minArgumentCount = -1; From a07a7c0b3cec17c4abadc1dad24b911c5b821656 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Fri, 22 Jan 2016 15:14:58 -0800 Subject: [PATCH 10/14] Remove unreachable code --- src/compiler/checker.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 26b80b1aeee38..fb083d5c5c222 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3966,10 +3966,6 @@ namespace ts { } } - if (declaration.typeParameters) { - return getTypeParametersFromDeclaration(declaration.typeParameters); - } - return undefined; } From 0d480a6e1b8c0bf8b8799ea70f2dab39c2ad3c88 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Fri, 22 Jan 2016 15:15:58 -0800 Subject: [PATCH 11/14] Remove value-side JSDoc type lookup fallback --- src/compiler/checker.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fb083d5c5c222..82b6acadc5a93 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4444,14 +4444,7 @@ namespace ts { return unknownSymbol; } - let symbol = resolveEntityName(typeReferenceName, SymbolFlags.Type); - if (!symbol && node.kind === SyntaxKind.JSDocTypeReference) { - // If the reference didn't resolve to a type, try seeing if results to a - // value. If it does, get the type of that value. - symbol = resolveEntityName(typeReferenceName, SymbolFlags.Value); - } - - return symbol || unknownSymbol; + return resolveEntityName(typeReferenceName, SymbolFlags.Type) || unknownSymbol; } function getTypeReferenceType(node: TypeReferenceNode | ExpressionWithTypeArguments | JSDocTypeReference, symbol: Symbol) { From d54f3cb7e7780e4fbb3dc045001a32ecff2c521f Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Fri, 22 Jan 2016 15:16:27 -0800 Subject: [PATCH 12/14] Remove obsoleted test --- tests/cases/fourslash/getJavaScriptQuickInfo7.ts | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 tests/cases/fourslash/getJavaScriptQuickInfo7.ts diff --git a/tests/cases/fourslash/getJavaScriptQuickInfo7.ts b/tests/cases/fourslash/getJavaScriptQuickInfo7.ts deleted file mode 100644 index 282f5aeabfd3d..0000000000000 --- a/tests/cases/fourslash/getJavaScriptQuickInfo7.ts +++ /dev/null @@ -1,10 +0,0 @@ -/// - -// @allowNonTsExtensions: true -// @Filename: Foo.js -//// function f(a,b) { } -//// /** @type {f} */ -//// var v/**/ - -goTo.marker(); -verify.quickInfoIs('var v: (a: any, b: any) => void'); \ No newline at end of file From 57fb5fa67bfd7edfc3e8279f2070a474477f58e1 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Fri, 22 Jan 2016 15:17:20 -0800 Subject: [PATCH 13/14] Consolidate branches --- src/compiler/checker.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 82b6acadc5a93..4ea8c71e0548a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7283,25 +7283,25 @@ namespace ts { return container.flags & NodeFlags.Static ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol)).thisType; } - if (container.parserContextFlags & ParserContextFlags.JavaScriptFile) { + if (isInJavaScriptFile(node)) { const type = getTypeForThisExpressionFromJSDoc(container); if (type && type !== unknownType) { return type; } - } - // 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 (isInJavaScriptFile(node) && container.kind === SyntaxKind.FunctionExpression) { - if (getSpecialPropertyAssignmentKind(container.parent) === SpecialPropertyAssignmentKind.PrototypeProperty) { - // Get the 'x' of 'x.prototype.y = f' (here, 'f' is 'container') - const className = (((container.parent as BinaryExpression) // x.protoype.y = f - .left as PropertyAccessExpression) // x.prototype.y - .expression as PropertyAccessExpression) // x.prototype - .expression; // x - const classSymbol = checkExpression(className).symbol; - if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) { - return getInferredClassType(classSymbol); + // 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) { + if (getSpecialPropertyAssignmentKind(container.parent) === SpecialPropertyAssignmentKind.PrototypeProperty) { + // Get the 'x' of 'x.prototype.y = f' (here, 'f' is 'container') + const className = (((container.parent as BinaryExpression) // x.protoype.y = f + .left as PropertyAccessExpression) // x.prototype.y + .expression as PropertyAccessExpression) // x.prototype + .expression; // x + const classSymbol = checkExpression(className).symbol; + if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) { + return getInferredClassType(classSymbol); + } } } } From 00398d6c9e0a326fe712d083149d99d306f73b4c Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Fri, 22 Jan 2016 15:41:00 -0800 Subject: [PATCH 14/14] Remove unrelated changes --- src/compiler/sys.ts | 2 ++ src/harness/harness.ts | 2 +- src/services/services.ts | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 094bc810036b2..bf25d39aa4395 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -605,6 +605,8 @@ namespace ts { return getWScriptSystem(); } else if (typeof process !== "undefined" && process.nextTick && !process.browser && typeof require !== "undefined") { + // process and process.nextTick checks if current environment is node-like + // process.browser check excludes webpack and browserify return getNodeSystem(); } else if (typeof ChakraHost !== "undefined") { diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 329834cb15d4c..7fd819731721a 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -1593,7 +1593,7 @@ namespace Harness { return { unitName: libFile, content: io.readFile(libFile) }; } - if (Error) (Error).stackTraceLimit = 25; + if (Error) (Error).stackTraceLimit = 1; } // TODO: not sure why Utils.evalFile isn't working with this, eventually will concat it like old compiler instead of eval diff --git a/src/services/services.ts b/src/services/services.ts index 1d0cc4a459750..0fae1c0aa45c6 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2188,6 +2188,7 @@ namespace ts { } return true; } + return false; } @@ -2370,7 +2371,6 @@ namespace ts { // skip open bracket token = scanner.scan(); - let i = 0; // scan until ']' or EOF while (token !== SyntaxKind.CloseBracketToken && token !== SyntaxKind.EndOfFileToken) {