Add callback tag, with type parameters (#23947)

* Add initial tests

* Add types

* Half of parsing (builds but does not pass tests)

* Parsing done; types are uglier; doesn't crash but doesn't pass

* Bind callback tag

Builds but tests still don't pass

* Only bind param tags inside callback tags

* Fix binding switch to only handle param tags once

* Checking is 1/3 done or so.

Now I'm going to go rename some members to be more uniform. I hate
unnnecessary conditionals.

* Rename typeExpression to type (for some jsdoc)

(maybe I'll rename more later)

* Rename the rest of typeExpressions

Turns out there is a constraint in services such that they all need to
be named the same.

* Few more checker changes

* Revert "Rename the rest of typeExpressions"

This reverts commit f41a96b24d.

* Revert "Rename typeExpression to type (for some jsdoc)"

This reverts commit 7d2233a00e.

* Finish undoing typeExpression rename

* Rename and improve getTypeParametersForAliasSymbol

Plus some other small fixes

* Core checking works, but is flabbergastingly messy

I'm serious.

* Callback return types work now

* Fix crash in services

* Make github diff smaller

* Try to make github diff even smaller

* Fix rename for callback tag

* Fix nav bar for callback tag

Also clean up some now-redundant code there to find the name of typedefs.

* Handle ooorder callback tags

Also get rid of redundant typedef name code *in the binder*. It's
everywhere!

* Add ooorder callback tag test

* Parse comments for typedef/callback+display param comments

* Always export callbacks

This requires almost no new code since it is basically the same as
typedefs

* Update baselines

* Fix support for nested namespaced callbacks

And add test

* Callbacks support type parameters

1. Haven't run it with all tests
2. Haven't tested typedef tags yet
3. Still allows shared symbols when on function or class declarations.

* Template tags are now bound correctly

* Test oorder template tags

It works.

* Parser cleanup

* Cleanup types and utilities

As much as possible, and not as much as I would like.

* Handle callback more often in services

* Cleanup of binder and checker

* More checker cleanup

* Remove TODOs and one more cleanup

* Support parameter-less callback tags

* Remove extra bind call on template type parameters

* Bind template tag containers

Doesn't quite work with typedefs, but that's because it's now stricter,
without the typedef fixes. I'm going to merge with jsdoc/callback and
see how it goes.

* Fix fourslash failures

* Stop pre-binding js type aliases

Next up, stop pre-binding js type parameters

* Further cleanup of delayed js type alias binding

* Stop prebinding template tags too

This gets rid of prebinding entirely

* Remove TODO

* Fix lint

* Finish merge with use-jsdoc-aliases

* Update callback tag baselines

* Rename getTypeParametersForAliasSymbol

The real fix is *probably* to rename Type.aliasTypeArguments to
aliasTypeParameters, but I want to make sure and then put it in a
separate PR.
This commit is contained in:
Nathan Shively-Sanders
2018-05-17 09:28:11 -07:00
committed by GitHub
parent dbd4ef4910
commit aa7e2b0f07
49 changed files with 1168 additions and 384 deletions

View File

@@ -118,7 +118,7 @@ namespace ts {
let thisParentContainer: Node; // Container one level up
let blockScopeContainer: Node;
let lastContainer: Node;
let delayedTypedefs: { typedef: JSDocTypedefTag, container: Node, lastContainer: Node, blockScopeContainer: Node, parent: Node }[];
let delayedTypeAliases: (JSDocTypedefTag | JSDocCallbackTag)[];
let seenThisKeyword: boolean;
// state used by control flow analysis
@@ -188,7 +188,7 @@ namespace ts {
thisParentContainer = undefined;
blockScopeContainer = undefined;
lastContainer = undefined;
delayedTypedefs = undefined;
delayedTypeAliases = undefined;
seenThisKeyword = false;
currentFlow = undefined;
currentBreakTarget = undefined;
@@ -273,6 +273,7 @@ namespace ts {
return InternalSymbolName.Constructor;
case SyntaxKind.FunctionType:
case SyntaxKind.CallSignature:
case SyntaxKind.JSDocSignature:
return InternalSymbolName.Call;
case SyntaxKind.ConstructorType:
case SyntaxKind.ConstructSignature:
@@ -301,9 +302,6 @@ namespace ts {
const functionType = <JSDocFunctionType>node.parent;
const index = functionType.parameters.indexOf(node as ParameterDeclaration);
return "arg" + index as __String;
case SyntaxKind.JSDocTypedefTag:
const name = getNameOfJSDocTypedef(node as JSDocTypedefTag);
return typeof name !== "undefined" ? name.escapedText : undefined;
}
}
@@ -456,8 +454,8 @@ namespace ts {
// during global merging in the checker. Why? The only case when ambient module is permitted inside another module is module augmentation
// and this case is specially handled. Module augmentations should only be merged with original module definition
// and should never be merged directly with other augmentation, and the latter case would be possible if automatic merge is allowed.
if (node.kind === SyntaxKind.JSDocTypedefTag) Debug.assert(isInJavaScriptFile(node)); // We shouldn't add symbols for JSDoc nodes if not in a JS file.
if ((!isAmbientModule(node) && (hasExportModifier || container.flags & NodeFlags.ExportContext)) || isJSDocTypedefTag(node)) {
if (isJSDocTypeAlias(node)) Debug.assert(isInJavaScriptFile(node)); // We shouldn't add symbols for JSDoc nodes if not in a JS file.
if ((!isAmbientModule(node) && (hasExportModifier || container.flags & NodeFlags.ExportContext)) || isJSDocTypeAlias(node)) {
if (hasModifier(node, ModifierFlags.Default) && !getDeclarationName(node)) {
return declareSymbol(container.symbol.exports, container.symbol, node, symbolFlags, symbolExcludes); // No local symbol for an unnamed default!
}
@@ -628,22 +626,6 @@ namespace ts {
}
function bindChildrenWorker(node: Node): void {
// Binding of JsDocComment should be done before the current block scope container changes.
// because the scope of JsDocComment should not be affected by whether the current node is a
// container or not.
if (hasJSDocNodes(node)) {
if (isInJavaScriptFile(node)) {
for (const j of node.jsDoc) {
bind(j);
}
}
else {
for (const j of node.jsDoc) {
setParentPointers(node, j);
}
}
}
if (checkUnreachable(node)) {
bindEachChild(node);
return;
@@ -709,11 +691,9 @@ namespace ts {
case SyntaxKind.CallExpression:
bindCallExpressionFlow(<CallExpression>node);
break;
case SyntaxKind.JSDocComment:
bindJSDocComment(<JSDoc>node);
break;
case SyntaxKind.JSDocTypedefTag:
bindJSDocTypedefTag(<JSDocTypedefTag>node);
case SyntaxKind.JSDocCallbackTag:
bindJSDocTypeAlias(node as JSDocTypedefTag | JSDocCallbackTag);
break;
// In source files and blocks, bind functions first to match hoisting that occurs at runtime
case SyntaxKind.SourceFile:
@@ -728,6 +708,7 @@ namespace ts {
bindEachChild(node);
break;
}
bindJSDoc(node);
}
function isNarrowingExpression(expr: Expression): boolean {
@@ -1379,24 +1360,10 @@ namespace ts {
}
}
function bindJSDocComment(node: JSDoc) {
forEachChild(node, n => {
if (n.kind !== SyntaxKind.JSDocTypedefTag) {
bind(n);
}
});
}
function bindJSDocTypedefTag(node: JSDocTypedefTag) {
forEachChild(node, n => {
// if the node has a fullName "A.B.C", that means symbol "C" was already bound
// when we visit "fullName"; so when we visit the name "C" as the next child of
// the jsDocTypedefTag, we should skip binding it.
if (node.fullName && n === node.name && node.fullName.kind !== SyntaxKind.Identifier) {
return;
}
bind(n);
});
function bindJSDocTypeAlias(node: JSDocTypedefTag | JSDocCallbackTag) {
if (node.fullName) {
setParentPointers(node, node.fullName);
}
}
function bindCallExpressionFlow(node: CallExpression) {
@@ -1456,6 +1423,7 @@ namespace ts {
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
case SyntaxKind.CallSignature:
case SyntaxKind.JSDocSignature:
case SyntaxKind.JSDocFunctionType:
case SyntaxKind.FunctionType:
case SyntaxKind.ConstructSignature:
@@ -1545,6 +1513,7 @@ namespace ts {
case SyntaxKind.ConstructorType:
case SyntaxKind.CallSignature:
case SyntaxKind.ConstructSignature:
case SyntaxKind.JSDocSignature:
case SyntaxKind.IndexSignature:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
@@ -1555,6 +1524,8 @@ namespace ts {
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
case SyntaxKind.JSDocFunctionType:
case SyntaxKind.JSDocTypedefTag:
case SyntaxKind.JSDocCallbackTag:
case SyntaxKind.TypeAliasDeclaration:
case SyntaxKind.MappedType:
// All the children of these container types are never visible through another
@@ -1650,7 +1621,7 @@ namespace ts {
return state;
}
function bindFunctionOrConstructorType(node: SignatureDeclaration): void {
function bindFunctionOrConstructorType(node: SignatureDeclaration | JSDocSignature): void {
// For a given function symbol "<...>(...) => T" we want to generate a symbol identical
// to the one we would get for: { <...>(...): T }
//
@@ -1752,21 +1723,34 @@ namespace ts {
}
function delayedBindJSDocTypedefTag() {
if (!delayedTypedefs) {
if (!delayedTypeAliases) {
return;
}
const saveContainer = container;
const saveLastContainer = lastContainer;
const saveBlockScopeContainer = blockScopeContainer;
const saveParent = parent;
for (const delay of delayedTypedefs) {
({ container, lastContainer, blockScopeContainer, parent } = delay);
bindBlockScopedDeclaration(delay.typedef, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes);
const saveCurrentFlow = currentFlow;
for (const typeAlias of delayedTypeAliases) {
const host = getJSDocHost(typeAlias);
container = findAncestor(host.parent, n => !!(getContainerFlags(n) & ContainerFlags.IsContainer)) || file;
blockScopeContainer = getEnclosingBlockScopeContainer(host) || file;
currentFlow = { flags: FlowFlags.Start };
parent = typeAlias;
bind(typeAlias.typeExpression);
if (!typeAlias.fullName || typeAlias.fullName.kind === SyntaxKind.Identifier) {
parent = typeAlias.parent;
bindBlockScopedDeclaration(typeAlias, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes);
}
else {
bind(typeAlias.fullName);
}
}
container = saveContainer;
lastContainer = saveLastContainer;
blockScopeContainer = saveBlockScopeContainer;
parent = saveParent;
currentFlow = saveCurrentFlow;
}
// The binder visits every node in the syntax tree so it is a convenient place to perform a single localized
@@ -1946,7 +1930,6 @@ namespace ts {
// Here the current node is "foo", which is a container, but the scope of "MyType" should
// not be inside "foo". Therefore we always bind @typedef before bind the parent node,
// and skip binding this tag later when binding all the other jsdoc tags.
if (isInJavaScriptFile(node)) bindJSDocTypedefTagIfAny(node);
// First we bind declaration nodes to a symbol if possible. We'll both create a symbol
// and then potentially add the symbol to an appropriate symbol table. Possible
@@ -1978,26 +1961,21 @@ namespace ts {
}
else if (!skipTransformFlagAggregation && (node.transformFlags & TransformFlags.HasComputedFlags) === 0) {
subtreeTransformFlags |= computeTransformFlagsForNode(node, 0);
bindJSDoc(node);
}
inStrictMode = saveInStrictMode;
}
function bindJSDocTypedefTagIfAny(node: Node) {
if (!hasJSDocNodes(node)) {
return;
}
for (const jsDoc of node.jsDoc) {
if (!jsDoc.tags) {
continue;
function bindJSDoc(node: Node) {
if (hasJSDocNodes(node)) {
if (isInJavaScriptFile(node)) {
for (const j of node.jsDoc) {
bind(j);
}
}
for (const tag of jsDoc.tags) {
if (tag.kind === SyntaxKind.JSDocTypedefTag) {
const savedParent = parent;
parent = jsDoc;
bind(tag);
parent = savedParent;
else {
for (const j of node.jsDoc) {
setParentPointers(node, j);
}
}
}
@@ -2036,10 +2014,10 @@ namespace ts {
// current "blockScopeContainer" needs to be set to its immediate namespace parent.
if ((<Identifier>node).isInJSDocNamespace) {
let parentNode = node.parent;
while (parentNode && parentNode.kind !== SyntaxKind.JSDocTypedefTag) {
while (parentNode && !isJSDocTypeAlias(parentNode)) {
parentNode = parentNode.parent;
}
bindBlockScopedDeclaration(<Declaration>parentNode, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes);
bindBlockScopedDeclaration(parentNode as Declaration, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes);
break;
}
// falls through
@@ -2141,8 +2119,9 @@ namespace ts {
return bindPropertyOrMethodOrAccessor(<Declaration>node, SymbolFlags.SetAccessor, SymbolFlags.SetAccessorExcludes);
case SyntaxKind.FunctionType:
case SyntaxKind.JSDocFunctionType:
case SyntaxKind.JSDocSignature:
case SyntaxKind.ConstructorType:
return bindFunctionOrConstructorType(<SignatureDeclaration>node);
return bindFunctionOrConstructorType(<SignatureDeclaration | JSDocSignature>node);
case SyntaxKind.TypeLiteral:
case SyntaxKind.JSDocTypeLiteral:
case SyntaxKind.MappedType:
@@ -2205,6 +2184,9 @@ namespace ts {
return updateStrictModeStatementList((<Block | ModuleBlock>node).statements);
case SyntaxKind.JSDocParameterTag:
if (node.parent.kind === SyntaxKind.JSDocSignature) {
return bindParameter(node as JSDocParameterTag);
}
if (node.parent.kind !== SyntaxKind.JSDocTypeLiteral) {
break;
}
@@ -2215,13 +2197,9 @@ namespace ts {
SymbolFlags.Property | SymbolFlags.Optional :
SymbolFlags.Property;
return declareSymbolAndAddToSymbolTable(propTag, flags, SymbolFlags.PropertyExcludes);
case SyntaxKind.JSDocTypedefTag: {
const { fullName } = node as JSDocTypedefTag;
if (!fullName || fullName.kind === SyntaxKind.Identifier) {
(delayedTypedefs || (delayedTypedefs = [])).push({ typedef: node as JSDocTypedefTag, container, lastContainer, blockScopeContainer, parent });
}
break;
}
case SyntaxKind.JSDocTypedefTag:
case SyntaxKind.JSDocCallbackTag:
return (delayedTypeAliases || (delayedTypeAliases = [])).push(node as JSDocTypedefTag | JSDocCallbackTag);
}
}
@@ -2622,7 +2600,10 @@ namespace ts {
}
}
function bindParameter(node: ParameterDeclaration) {
function bindParameter(node: ParameterDeclaration | JSDocParameterTag) {
if (node.kind === SyntaxKind.JSDocParameterTag && container.kind !== SyntaxKind.JSDocSignature) {
return;
}
if (inStrictMode && !(node.flags & NodeFlags.Ambient)) {
// It is a SyntaxError if the identifier eval or arguments appears within a FormalParameterList of a
// strict mode FunctionLikeDeclaration or FunctionExpression(13.1)
@@ -2630,7 +2611,7 @@ namespace ts {
}
if (isBindingPattern(node.name)) {
bindAnonymousDeclaration(node, SymbolFlags.FunctionScopedVariable, "__" + node.parent.parameters.indexOf(node) as __String);
bindAnonymousDeclaration(node, SymbolFlags.FunctionScopedVariable, "__" + (node as ParameterDeclaration).parent.parameters.indexOf(node as ParameterDeclaration) as __String);
}
else {
declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.ParameterExcludes);
@@ -2690,18 +2671,24 @@ namespace ts {
}
function getInferTypeContainer(node: Node): ConditionalTypeNode {
while (node) {
const parent = node.parent;
if (parent && parent.kind === SyntaxKind.ConditionalType && (<ConditionalTypeNode>parent).extendsType === node) {
return <ConditionalTypeNode>parent;
}
node = parent;
}
return undefined;
const extendsType = findAncestor(node, n => n.parent && isConditionalTypeNode(n.parent) && n.parent.extendsType === n);
return extendsType && extendsType.parent as ConditionalTypeNode;
}
function bindTypeParameter(node: TypeParameterDeclaration) {
if (node.parent.kind === SyntaxKind.InferType) {
if (isJSDocTemplateTag(node.parent)) {
const container = find((node.parent.parent as JSDoc).tags, isJSDocTypeAlias) || getHostSignatureFromJSDoc(node.parent);
if (container) {
if (!container.locals) {
container.locals = createSymbolTable();
}
declareSymbol(container.locals, /*parent*/ undefined, node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes);
}
else {
declareSymbolAndAddToSymbolTable(node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes);
}
}
else if (node.parent.kind === SyntaxKind.InferType) {
const container = getInferTypeContainer(node.parent);
if (container) {
if (!container.locals) {
@@ -3804,6 +3791,6 @@ namespace ts {
*/
function setParentPointers(parent: Node, child: Node): void {
child.parent = parent;
forEachChild(child, (childsChild) => setParentPointers(child, childsChild));
forEachChild(child, grandchild => setParentPointers(child, grandchild));
}
}

View File

@@ -1577,9 +1577,11 @@ namespace ts {
function isTypeParameterSymbolDeclaredInContainer(symbol: Symbol, container: Node) {
for (const decl of symbol.declarations) {
const parent = isJSDocTemplateTag(decl.parent) ? getJSDocHost(decl.parent) : decl.parent;
if (decl.kind === SyntaxKind.TypeParameter && parent === container) {
return true;
if (decl.kind === SyntaxKind.TypeParameter) {
const parent = isJSDocTemplateTag(decl.parent) ? getJSDocHost(decl.parent) : decl.parent;
if (parent === container) {
return !(isJSDocTemplateTag(decl.parent) && find((decl.parent.parent as JSDoc).tags, isJSDocTypeAlias));
}
}
}
@@ -2089,7 +2091,7 @@ namespace ts {
let symbol: Symbol;
if (name.kind === SyntaxKind.Identifier) {
const message = meaning === namespaceMeaning ? Diagnostics.Cannot_find_namespace_0 : Diagnostics.Cannot_find_name_0;
const symbolFromJSPrototype = isInJavaScriptFile(name) ? resolveEntityNameFromJSPrototype(name, meaning) : undefined;
const symbolFromJSPrototype = isInJavaScriptFile(name) ? resolveEntityNameFromJSSpecialAssignment(name, meaning) : undefined;
symbol = resolveName(location || name, name.escapedText, meaning, ignoreErrors || symbolFromJSPrototype ? undefined : message, name, /*isUse*/ true);
if (!symbol) {
return symbolFromJSPrototype;
@@ -2144,25 +2146,34 @@ namespace ts {
}
/**
* For prototype-property methods like `A.prototype.m = function () ...`, try to resolve names in the scope of `A` too.
* 1. For prototype-property methods like `A.prototype.m = function () ...`, try to resolve names in the scope of `A` too.
* Note that prototype-property assignment to locations outside the current file (eg globals) doesn't work, so
* name resolution won't work either.
* 2. For property assignments like `{ x: function f () { } }`, try to resolve names in the scope of `f` too.
*/
function resolveEntityNameFromJSPrototype(name: Identifier, meaning: SymbolFlags) {
if (isJSDocTypeReference(name.parent) && isJSDocTag(name.parent.parent.parent)) {
const host = getJSDocHost(name.parent.parent.parent as JSDocTag);
if (isExpressionStatement(host) &&
isBinaryExpression(host.expression) &&
getSpecialPropertyAssignmentKind(host.expression) === SpecialPropertyAssignmentKind.PrototypeProperty) {
const symbol = getSymbolOfNode(host.expression.left);
if (symbol) {
const secondaryLocation = symbol.parent.valueDeclaration;
return resolveName(secondaryLocation, name.escapedText, meaning, /*nameNotFoundMessage*/ undefined, name, /*isUse*/ true);
}
function resolveEntityNameFromJSSpecialAssignment(name: Identifier, meaning: SymbolFlags) {
if (isJSDocTypeReference(name.parent)) {
const host = getJSDocHost(name.parent);
if (host) {
const secondaryLocation = getJSSpecialAssignmentSymbol(getJSDocHost(name.parent.parent.parent as JSDocTag));
return secondaryLocation && resolveName(secondaryLocation, name.escapedText, meaning, /*nameNotFoundMessage*/ undefined, name, /*isUse*/ true);
}
}
}
function getJSSpecialAssignmentSymbol(host: HasJSDoc): Declaration | undefined {
if (isPropertyAssignment(host) && isFunctionLike(host.initializer)) {
const symbol = getSymbolOfNode(host.initializer);
return symbol && symbol.valueDeclaration;
}
else if (isExpressionStatement(host) &&
isBinaryExpression(host.expression) &&
getSpecialPropertyAssignmentKind(host.expression) === SpecialPropertyAssignmentKind.PrototypeProperty) {
const symbol = getSymbolOfNode(host.expression.left);
return symbol && symbol.parent.valueDeclaration;
}
}
function resolveExternalModuleName(location: Node, moduleReferenceExpression: Expression): Symbol {
return resolveExternalModuleNameWorker(location, moduleReferenceExpression, Diagnostics.Cannot_find_module_0);
}
@@ -3606,8 +3617,10 @@ namespace ts {
}
function symbolToParameterDeclaration(parameterSymbol: Symbol, context: NodeBuilderContext, preserveModifierFlags?: boolean): ParameterDeclaration {
const parameterDeclaration = getDeclarationOfKind<ParameterDeclaration>(parameterSymbol, SyntaxKind.Parameter);
Debug.assert(!!parameterDeclaration || isTransientSymbol(parameterSymbol));
let parameterDeclaration: ParameterDeclaration | JSDocParameterTag = getDeclarationOfKind<ParameterDeclaration>(parameterSymbol, SyntaxKind.Parameter);
if (!parameterDeclaration && !isTransientSymbol(parameterSymbol)) {
parameterDeclaration = getDeclarationOfKind<JSDocParameterTag>(parameterSymbol, SyntaxKind.JSDocParameterTag);
}
let parameterType = getTypeOfSymbol(parameterSymbol);
if (parameterDeclaration && isRequiredInitializedParameter(parameterDeclaration)) {
@@ -3620,8 +3633,8 @@ namespace ts {
const dotDotDotToken = isRest ? createToken(SyntaxKind.DotDotDotToken) : undefined;
const name = parameterDeclaration
? parameterDeclaration.name ?
parameterDeclaration.name.kind === SyntaxKind.Identifier ?
setEmitFlags(getSynthesizedClone(parameterDeclaration.name), EmitFlags.NoAsciiEscaping) :
parameterDeclaration.name.kind === SyntaxKind.Identifier ? setEmitFlags(getSynthesizedClone(parameterDeclaration.name), EmitFlags.NoAsciiEscaping) :
parameterDeclaration.name.kind === SyntaxKind.QualifiedName ? setEmitFlags(getSynthesizedClone(parameterDeclaration.name.right), EmitFlags.NoAsciiEscaping) :
cloneBindingName(parameterDeclaration.name) :
symbolName(parameterSymbol)
: symbolName(parameterSymbol);
@@ -4026,8 +4039,9 @@ namespace ts {
function determineIfDeclarationIsVisible() {
switch (node.kind) {
case SyntaxKind.JSDocCallbackTag:
case SyntaxKind.JSDocTypedefTag:
// Top-level jsdoc typedefs are considered exported
// Top-level jsdoc type aliases are considered exported
// First parent is comment node, second is hosting declaration or token; we only care about those tokens or declarations whose parent is a source file
return !!(node.parent && node.parent.parent && node.parent.parent.parent && isSourceFile(node.parent.parent.parent));
case SyntaxKind.BindingElement:
@@ -4802,7 +4816,7 @@ namespace ts {
if (symbol.flags & SymbolFlags.Prototype) {
return links.type = getTypeOfPrototypeProperty(symbol);
}
// CommonsJS require/module/exports all have type any.
// CommonsJS require and module both have type any.
if (symbol === requireSymbol || symbol === moduleSymbol) {
return links.type = anyType;
}
@@ -4837,7 +4851,7 @@ namespace ts {
declaration.kind === SyntaxKind.PropertyAccessExpression && declaration.parent.kind === SyntaxKind.BinaryExpression) {
type = getWidenedTypeFromJSSpecialPropertyDeclarations(symbol);
}
else if (isJSDocPropertyTag(declaration)
else if (isJSDocPropertyLikeTag(declaration)
|| isPropertyAccessExpression(declaration)
|| isIdentifier(declaration)
|| (isMethodDeclaration(declaration) && !isObjectLiteralMethod(declaration))
@@ -5139,6 +5153,8 @@ namespace ts {
case SyntaxKind.ArrowFunction:
case SyntaxKind.TypeAliasDeclaration:
case SyntaxKind.JSDocTemplateTag:
case SyntaxKind.JSDocTypedefTag:
case SyntaxKind.JSDocCallbackTag:
case SyntaxKind.MappedType:
case SyntaxKind.ConditionalType:
const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes);
@@ -5168,9 +5184,11 @@ namespace ts {
function getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol: Symbol): TypeParameter[] {
let result: TypeParameter[];
for (const node of symbol.declarations) {
if (node.kind === SyntaxKind.InterfaceDeclaration || node.kind === SyntaxKind.ClassDeclaration ||
node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.TypeAliasDeclaration || node.kind === SyntaxKind.JSDocTypedefTag) {
const declaration = <InterfaceDeclaration | TypeAliasDeclaration | JSDocTypedefTag>node;
if (node.kind === SyntaxKind.InterfaceDeclaration ||
node.kind === SyntaxKind.ClassDeclaration ||
node.kind === SyntaxKind.ClassExpression ||
isTypeAlias(node)) {
const declaration = <InterfaceDeclaration | TypeAliasDeclaration | JSDocTypedefTag | JSDocCallbackTag>node;
const typeParameters = getEffectiveTypeParameterDeclarations(declaration);
if (typeParameters) {
result = appendTypeParameters(result, typeParameters);
@@ -5466,9 +5484,9 @@ namespace ts {
return unknownType;
}
const declaration = <JSDocTypedefTag | TypeAliasDeclaration>find(symbol.declarations, d =>
d.kind === SyntaxKind.JSDocTypedefTag || d.kind === SyntaxKind.TypeAliasDeclaration);
const typeNode = declaration.kind === SyntaxKind.JSDocTypedefTag ? declaration.typeExpression : declaration.type;
const declaration = <JSDocTypedefTag | JSDocCallbackTag | TypeAliasDeclaration>find(symbol.declarations, d =>
isJSDocTypeAlias(d) || d.kind === SyntaxKind.TypeAliasDeclaration);
const typeNode = isJSDocTypeAlias(declaration) ? declaration.typeExpression : declaration.type;
// If typeNode is missing, we will error in checkJSDocTypedefTag.
let type = typeNode ? getTypeFromTypeNode(typeNode) : unknownType;
@@ -6036,7 +6054,7 @@ namespace ts {
}
function createSignature(
declaration: SignatureDeclaration,
declaration: SignatureDeclaration | JSDocSignature,
typeParameters: TypeParameter[],
thisParameter: Symbol | undefined,
parameters: Symbol[],
@@ -7046,8 +7064,8 @@ namespace ts {
return symbol && withAugmentations ? getMergedSymbol(symbol) : symbol;
}
function isOptionalParameter(node: ParameterDeclaration) {
if (hasQuestionToken(node) || isJSDocOptionalParameter(node)) {
function isOptionalParameter(node: ParameterDeclaration | JSDocParameterTag) {
if (hasQuestionToken(node) || isOptionalJSDocParameterTag(node) || isJSDocOptionalParameter(node)) {
return true;
}
@@ -7067,6 +7085,14 @@ namespace ts {
return false;
}
function isOptionalJSDocParameterTag(node: Node): node is JSDocParameterTag {
if (!isJSDocParameterTag(node)) {
return false;
}
const { isBracketed, typeExpression } = node;
return isBracketed || !!typeExpression && typeExpression.type.kind === SyntaxKind.JSDocOptionalType;
}
function createTypePredicateFromTypePredicateNode(node: TypePredicateNode): IdentifierTypePredicate | ThisTypePredicate {
const { parameterName } = node;
const type = getTypeFromTypeNode(node.type);
@@ -7142,7 +7168,7 @@ namespace ts {
return typeArguments;
}
function getSignatureFromDeclaration(declaration: SignatureDeclaration): Signature {
function getSignatureFromDeclaration(declaration: SignatureDeclaration | JSDocSignature): Signature {
const links = getNodeLinks(declaration);
if (!links.resolvedSignature) {
const parameters: Symbol[] = [];
@@ -7165,6 +7191,7 @@ namespace ts {
const param = declaration.parameters[i];
let paramSymbol = param.symbol;
const type = isJSDocParameterTag(param) ? (param.typeExpression && param.typeExpression.type) : param.type;
// Include parameter symbol instead of property symbol in the signature
if (paramSymbol && !!(paramSymbol.flags & SymbolFlags.Property) && !isBindingPattern(param.name)) {
const resolvedSymbol = resolveName(param, paramSymbol.escapedName, SymbolFlags.Value, undefined, undefined, /*isUse*/ false);
@@ -7178,13 +7205,14 @@ namespace ts {
parameters.push(paramSymbol);
}
if (param.type && param.type.kind === SyntaxKind.LiteralType) {
if (type && type.kind === SyntaxKind.LiteralType) {
hasLiteralTypes = true;
}
// Record a new minimum argument count if this is not an optional parameter
const isOptionalParameter = param.initializer || param.questionToken || param.dotDotDotToken ||
iife && parameters.length > iife.arguments.length && !param.type ||
const isOptionalParameter = isOptionalJSDocParameterTag(param) ||
param.initializer || param.questionToken || param.dotDotDotToken ||
iife && parameters.length > iife.arguments.length && !type ||
isUntypedSignatureInJSFile ||
isJSDocOptionalParameter(param);
if (!isOptionalParameter) {
@@ -7220,8 +7248,8 @@ namespace ts {
* OR
* 2. It has at least one parameter, and the last parameter has a matching `@param` with a type that starts with `...`
*/
function maybeAddJsSyntheticRestParameter(declaration: SignatureDeclaration, parameters: Symbol[]): boolean {
if (!containsArgumentsReference(declaration)) {
function maybeAddJsSyntheticRestParameter(declaration: SignatureDeclaration | JSDocSignature, parameters: Symbol[]): boolean {
if (isJSDocSignature(declaration) || !containsArgumentsReference(declaration)) {
return false;
}
const lastParam = lastOrUndefined(declaration.parameters);
@@ -7240,9 +7268,9 @@ namespace ts {
return true;
}
function getSignatureReturnTypeFromDeclaration(declaration: SignatureDeclaration, isJSConstructSignature: boolean, classType: Type) {
function getSignatureReturnTypeFromDeclaration(declaration: SignatureDeclaration | JSDocSignature, isJSConstructSignature: boolean, classType: Type) {
if (isJSConstructSignature) {
return getTypeFromTypeNode(declaration.parameters[0].type);
return getTypeFromTypeNode((declaration.parameters[0] as ParameterDeclaration).type);
}
else if (classType) {
return classType;
@@ -7656,11 +7684,11 @@ namespace ts {
const missingAugmentsTag = isJs && node.parent.kind !== SyntaxKind.JSDocAugmentsTag;
const diag = minTypeArgumentCount === typeParameters.length
? missingAugmentsTag
? Diagnostics.Expected_0_type_arguments_provide_these_with_an_extends_tag
: Diagnostics.Generic_type_0_requires_1_type_argument_s
: missingAugmentsTag
? Diagnostics.Expected_0_1_type_arguments_provide_these_with_an_extends_tag
: Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments;
? Diagnostics.Expected_0_type_arguments_provide_these_with_an_extends_tag
: Diagnostics.Generic_type_0_requires_1_type_argument_s
: missingAugmentsTag
? Diagnostics.Expected_0_1_type_arguments_provide_these_with_an_extends_tag
: Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments;
const typeStr = typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType);
error(node, diag, typeStr, minTypeArgumentCount, typeParameters.length);
if (!isJs) {
@@ -7675,7 +7703,7 @@ namespace ts {
return createTypeReference(<GenericType>type, typeArguments);
}
return checkNoTypeArguments(node, symbol) ? type : unknownType;
}
}
function getTypeAliasInstantiation(symbol: Symbol, typeArguments: Type[]): Type {
const type = getDeclaredTypeOfSymbol(symbol);
@@ -7702,18 +7730,18 @@ namespace ts {
const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters);
if (numTypeArguments < minTypeArgumentCount || numTypeArguments > typeParameters.length) {
error(node,
minTypeArgumentCount === typeParameters.length
? Diagnostics.Generic_type_0_requires_1_type_argument_s
: Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments,
symbolToString(symbol),
minTypeArgumentCount,
typeParameters.length);
minTypeArgumentCount === typeParameters.length
? Diagnostics.Generic_type_0_requires_1_type_argument_s
: Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments,
symbolToString(symbol),
minTypeArgumentCount,
typeParameters.length);
return unknownType;
}
return getTypeAliasInstantiation(symbol, typeArguments);
}
return checkNoTypeArguments(node, symbol) ? type : unknownType;
}
}
function getTypeReferenceName(node: TypeReferenceType): EntityNameOrEntityNameExpression | undefined {
switch (node.kind) {
@@ -7828,7 +7856,7 @@ namespace ts {
function getConstrainedTypeVariable(typeVariable: TypeVariable, node: Node) {
let constraints: Type[];
while (node && !isStatement(node)) {
while (node && !isStatement(node) && node.kind !== SyntaxKind.JSDocComment) {
const parent = node.parent;
if (parent.kind === SyntaxKind.ConditionalType && node === (<ConditionalTypeNode>parent).trueType) {
const constraint = getImpliedConstraint(typeVariable, (<ConditionalTypeNode>parent).checkType, (<ConditionalTypeNode>parent).extendsType);
@@ -8396,8 +8424,9 @@ namespace ts {
function getTypeFromUnionTypeNode(node: UnionTypeNode): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
const aliasSymbol = getAliasSymbolForTypeNode(node);
links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), UnionReduction.Literal,
getAliasSymbolForTypeNode(node), getAliasTypeArgumentsForTypeNode(node));
aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol));
}
return links.resolvedType;
}
@@ -8506,8 +8535,9 @@ namespace ts {
function getTypeFromIntersectionTypeNode(node: IntersectionTypeNode): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
const aliasSymbol = getAliasSymbolForTypeNode(node);
links.resolvedType = getIntersectionType(map(node.types, getTypeFromTypeNode),
getAliasSymbolForTypeNode(node), getAliasTypeArgumentsForTypeNode(node));
aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol));
}
return links.resolvedType;
}
@@ -8816,7 +8846,7 @@ namespace ts {
const type = <MappedType>createObjectType(ObjectFlags.Mapped, node.symbol);
type.declaration = node;
type.aliasSymbol = getAliasSymbolForTypeNode(node);
type.aliasTypeArguments = getAliasTypeArgumentsForTypeNode(node);
type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(type.aliasSymbol);
links.resolvedType = type;
// Eagerly resolve the constraint type which forces an error if the constraint type circularly
// references itself through one or more type aliases.
@@ -8924,7 +8954,8 @@ namespace ts {
const links = getNodeLinks(node);
if (!links.resolvedType) {
const checkType = getTypeFromTypeNode(node.checkType);
const aliasTypeArguments = getAliasTypeArgumentsForTypeNode(node);
const aliasSymbol = getAliasSymbolForTypeNode(node);
const aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol);
const allOuterTypeParameters = getOuterTypeParameters(node, /*includeThisTypes*/ true);
const outerTypeParameters = aliasTypeArguments ? allOuterTypeParameters : filter(allOuterTypeParameters, tp => isPossiblyReferencedInConditionalType(tp, node));
const root: ConditionalRoot = {
@@ -8937,7 +8968,7 @@ namespace ts {
inferTypeParameters: getInferTypeParameters(node),
outerTypeParameters,
instantiations: undefined,
aliasSymbol: getAliasSymbolForTypeNode(node),
aliasSymbol,
aliasTypeArguments
};
links.resolvedType = getConditionalType(root, /*mapper*/ undefined);
@@ -9041,7 +9072,7 @@ namespace ts {
else {
let type = createObjectType(ObjectFlags.Anonymous, node.symbol);
type.aliasSymbol = aliasSymbol;
type.aliasTypeArguments = getAliasTypeArgumentsForTypeNode(node);
type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol);
if (isJSDocTypeLiteral(node) && node.isArrayType) {
type = createArrayType(type);
}
@@ -9052,11 +9083,10 @@ namespace ts {
}
function getAliasSymbolForTypeNode(node: TypeNode) {
return (node.parent.kind === SyntaxKind.TypeAliasDeclaration || node.parent.kind === SyntaxKind.JSDocTypedefTag) ? getSymbolOfNode(node.parent) : undefined;
return isTypeAlias(node.parent) ? getSymbolOfNode(node.parent) : undefined;
}
function getAliasTypeArgumentsForTypeNode(node: TypeNode) {
const symbol = getAliasSymbolForTypeNode(node);
function getTypeArgumentsForAliasSymbol(symbol: Symbol) {
return symbol ? getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol) : undefined;
}
@@ -9314,6 +9344,7 @@ namespace ts {
case SyntaxKind.TypeLiteral:
case SyntaxKind.JSDocTypeLiteral:
case SyntaxKind.JSDocFunctionType:
case SyntaxKind.JSDocSignature:
return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node);
case SyntaxKind.TypeOperator:
return getTypeFromTypeOperatorNode(<TypeOperatorNode>node);
@@ -9991,8 +10022,8 @@ namespace ts {
function compareTypePredicateRelatedTo(
source: TypePredicate,
target: TypePredicate,
sourceDeclaration: SignatureDeclaration,
targetDeclaration: SignatureDeclaration,
sourceDeclaration: SignatureDeclaration | JSDocSignature,
targetDeclaration: SignatureDeclaration | JSDocSignature,
reportErrors: boolean,
errorReporter: ErrorReporter,
compareTypes: (s: Type, t: Type, reportErrors?: boolean) => Ternary): Ternary {
@@ -21761,8 +21792,9 @@ namespace ts {
switch (d.kind) {
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.TypeAliasDeclaration:
// A jsdoc typedef is, by definition, a type alias
// A jsdoc typedef and callback are, by definition, type aliases
case SyntaxKind.JSDocTypedefTag:
case SyntaxKind.JSDocCallbackTag:
return DeclarationSpaces.ExportType;
case SyntaxKind.ModuleDeclaration:
return isAmbientModule(d as ModuleDeclaration) || getModuleInstanceState(d as ModuleDeclaration) !== ModuleInstanceState.NonInstantiated
@@ -22297,7 +22329,7 @@ namespace ts {
}
}
function checkJSDocTypedefTag(node: JSDocTypedefTag) {
function checkJSDocTypeAliasTag(node: JSDocTypedefTag | JSDocCallbackTag) {
if (!node.typeExpression) {
// If the node had `@property` tags, `typeExpression` would have been set to the first property tag.
error(node.name, Diagnostics.JSDoc_typedef_tag_should_either_have_a_type_annotation_or_be_followed_by_property_or_member_tags);
@@ -25212,7 +25244,8 @@ namespace ts {
case SyntaxKind.JSDocAugmentsTag:
return checkJSDocAugmentsTag(node as JSDocAugmentsTag);
case SyntaxKind.JSDocTypedefTag:
return checkJSDocTypedefTag(node as JSDocTypedefTag);
case SyntaxKind.JSDocCallbackTag:
return checkJSDocTypeAliasTag(node as JSDocTypedefTag);
case SyntaxKind.JSDocParameterTag:
return checkJSDocParameterTag(node as JSDocParameterTag);
case SyntaxKind.JSDocFunctionType:
@@ -26423,9 +26456,10 @@ namespace ts {
return false;
}
function isRequiredInitializedParameter(parameter: ParameterDeclaration) {
function isRequiredInitializedParameter(parameter: ParameterDeclaration | JSDocParameterTag) {
return strictNullChecks &&
!isOptionalParameter(parameter) &&
!isJSDocParameterTag(parameter) &&
parameter.initializer &&
!hasModifier(parameter, ModifierFlags.ParameterPropertyModifier);
}

View File

@@ -489,6 +489,15 @@ namespace ts {
return visitNode(cbNode, (<JSDocTypedefTag>node).fullName) ||
visitNode(cbNode, (<JSDocTypedefTag>node).typeExpression);
}
case SyntaxKind.JSDocCallbackTag:
return visitNode(cbNode, (node as JSDocCallbackTag).fullName) ||
visitNode(cbNode, (node as JSDocCallbackTag).typeExpression);
case SyntaxKind.JSDocSignature:
return visitNodes(cbNode, cbNodes, node.decorators) ||
visitNodes(cbNode, cbNodes, node.modifiers) ||
visitNodes(cbNode, cbNodes, (<SignatureDeclaration>node).typeParameters) ||
visitNodes(cbNode, cbNodes, (<SignatureDeclaration>node).parameters) ||
visitNode(cbNode, (<SignatureDeclaration>node).type);
case SyntaxKind.JSDocTypeLiteral:
if ((node as JSDocTypeLiteral).jsDocPropertyTags) {
for (const tag of (node as JSDocTypeLiteral).jsDocPropertyTags) {
@@ -6331,8 +6340,9 @@ namespace ts {
}
const enum PropertyLikeParse {
Property,
Parameter,
Property = 1 << 0,
Parameter = 1 << 1,
CallbackParameter = 1 << 2,
}
export function parseJSDocCommentWorker(start: number, length: number): JSDoc {
@@ -6386,7 +6396,7 @@ namespace ts {
case SyntaxKind.AtToken:
if (state === JSDocState.BeginningOfLine || state === JSDocState.SawAsterisk) {
removeTrailingNewlines(comments);
parseTag(indent);
addTag(parseTag(indent));
// NOTE: According to usejsdoc.org, a tag goes to end of line, except the last tag.
// Real-world comments may break this rule, so "BeginningOfLine" will not be a real line beginning
// for malformed examples like `/** @param {string} x @returns {number} the length */`
@@ -6503,8 +6513,7 @@ namespace ts {
case "arg":
case "argument":
case "param":
addTag(parseParameterOrPropertyTag(atToken, tagName, PropertyLikeParse.Parameter, indent));
return;
return parseParameterOrPropertyTag(atToken, tagName, PropertyLikeParse.Parameter, indent);
case "return":
case "returns":
tag = parseReturnTag(atToken, tagName);
@@ -6516,7 +6525,10 @@ namespace ts {
tag = parseTypeTag(atToken, tagName);
break;
case "typedef":
tag = parseTypedefTag(atToken, tagName);
tag = parseTypedefTag(atToken, tagName, indent);
break;
case "callback":
tag = parseCallbackTag(atToken, tagName, indent);
break;
default:
tag = parseUnknownTag(atToken, tagName);
@@ -6531,8 +6543,11 @@ namespace ts {
// a badly malformed tag should not be added to the list of tags
return;
}
tag.comment = parseTagComments(indent + tag.end - tag.pos);
addTag(tag);
if (!tag.comment) {
// some tags, like typedef and callback, have already parsed their comments earlier
tag.comment = parseTagComments(indent + tag.end - tag.pos);
}
return tag;
}
function parseTagComments(indent: number): string | undefined {
@@ -6605,6 +6620,9 @@ namespace ts {
}
function addTag(tag: JSDocTag): void {
if (!tag) {
return;
}
if (!tags) {
tags = [tag];
tagsPos = tag.pos;
@@ -6665,9 +6683,9 @@ namespace ts {
typeExpression = tryParseTypeExpression();
}
const result = target === PropertyLikeParse.Parameter ?
<JSDocParameterTag>createNode(SyntaxKind.JSDocParameterTag, atToken.pos) :
<JSDocPropertyTag>createNode(SyntaxKind.JSDocPropertyTag, atToken.pos);
const result = target === PropertyLikeParse.Property ?
<JSDocPropertyTag>createNode(SyntaxKind.JSDocPropertyTag, atToken.pos) :
<JSDocParameterTag>createNode(SyntaxKind.JSDocParameterTag, atToken.pos);
let comment: string | undefined;
if (indent !== undefined) comment = parseTagComments(indent + scanner.getStartPos() - atToken.pos);
const nestedTypeLiteral = parseNestedTypeLiteral(typeExpression, name, target);
@@ -6771,27 +6789,17 @@ namespace ts {
return finishNode(tag);
}
function parseTypedefTag(atToken: AtToken, tagName: Identifier): JSDocTypedefTag {
function parseTypedefTag(atToken: AtToken, tagName: Identifier, indent: number): JSDocTypedefTag {
const typeExpression = tryParseTypeExpression();
skipWhitespace();
const typedefTag = <JSDocTypedefTag>createNode(SyntaxKind.JSDocTypedefTag, atToken.pos);
typedefTag.atToken = atToken;
typedefTag.tagName = tagName;
typedefTag.fullName = parseJSDocTypeNameWithNamespace(/*flags*/ 0);
if (typedefTag.fullName) {
let rightNode = typedefTag.fullName;
while (true) {
if (rightNode.kind === SyntaxKind.Identifier || !rightNode.body) {
// if node is identifier - use it as name
// otherwise use name of the rightmost part that we were able to parse
typedefTag.name = rightNode.kind === SyntaxKind.Identifier ? rightNode : rightNode.name;
break;
}
rightNode = rightNode.body;
}
}
typedefTag.fullName = parseJSDocTypeNameWithNamespace();
typedefTag.name = getJSDocTypeAliasName(typedefTag.fullName);
skipWhitespace();
typedefTag.comment = parseTagComments(indent);
typedefTag.typeExpression = typeExpression;
if (!typeExpression || isObjectOrObjectArrayTypeReference(typeExpression.type)) {
@@ -6826,23 +6834,69 @@ namespace ts {
}
return finishNode(typedefTag);
}
function parseJSDocTypeNameWithNamespace(flags: NodeFlags) {
const pos = scanner.getTokenPos();
const typeNameOrNamespaceName = parseJSDocIdentifierName();
function parseJSDocTypeNameWithNamespace(nested?: boolean) {
const pos = scanner.getTokenPos();
const typeNameOrNamespaceName = parseJSDocIdentifierName();
if (typeNameOrNamespaceName && parseOptional(SyntaxKind.DotToken)) {
const jsDocNamespaceNode = <JSDocNamespaceDeclaration>createNode(SyntaxKind.ModuleDeclaration, pos);
jsDocNamespaceNode.flags |= flags;
jsDocNamespaceNode.name = typeNameOrNamespaceName;
jsDocNamespaceNode.body = parseJSDocTypeNameWithNamespace(NodeFlags.NestedNamespace);
return finishNode(jsDocNamespaceNode);
if (typeNameOrNamespaceName && parseOptional(SyntaxKind.DotToken)) {
const jsDocNamespaceNode = <JSDocNamespaceDeclaration>createNode(SyntaxKind.ModuleDeclaration, pos);
if (nested) {
jsDocNamespaceNode.flags |= NodeFlags.NestedNamespace;
}
jsDocNamespaceNode.name = typeNameOrNamespaceName;
jsDocNamespaceNode.body = parseJSDocTypeNameWithNamespace(/*nested*/ true);
return finishNode(jsDocNamespaceNode);
}
if (typeNameOrNamespaceName && flags & NodeFlags.NestedNamespace) {
typeNameOrNamespaceName.isInJSDocNamespace = true;
if (typeNameOrNamespaceName && nested) {
typeNameOrNamespaceName.isInJSDocNamespace = true;
}
return typeNameOrNamespaceName;
}
function parseCallbackTag(atToken: AtToken, tagName: Identifier, indent: number): JSDocCallbackTag {
const callbackTag = createNode(SyntaxKind.JSDocCallbackTag, atToken.pos) as JSDocCallbackTag;
callbackTag.atToken = atToken;
callbackTag.tagName = tagName;
callbackTag.fullName = parseJSDocTypeNameWithNamespace();
callbackTag.name = getJSDocTypeAliasName(callbackTag.fullName);
skipWhitespace();
callbackTag.comment = parseTagComments(indent);
let child: JSDocParameterTag | false;
const start = scanner.getStartPos();
const jsdocSignature = createNode(SyntaxKind.JSDocSignature, start) as JSDocSignature;
jsdocSignature.parameters = [];
while (child = tryParse(() => parseChildParameterOrPropertyTag(PropertyLikeParse.CallbackParameter) as JSDocParameterTag)) {
jsdocSignature.parameters = append(jsdocSignature.parameters as MutableNodeArray<JSDocParameterTag>, child);
}
const returnTag = tryParse(() => {
if (token() === SyntaxKind.AtToken) {
nextJSDocToken();
const tag = parseTag(indent);
if (tag && tag.kind === SyntaxKind.JSDocReturnTag) {
return tag as JSDocReturnTag;
}
}
});
if (returnTag) {
jsdocSignature.type = returnTag;
}
callbackTag.typeExpression = finishNode(jsdocSignature);
return finishNode(callbackTag);
}
function getJSDocTypeAliasName(fullName: JSDocNamespaceBody | undefined) {
if (fullName) {
let rightNode = fullName;
while (true) {
if (ts.isIdentifier(rightNode) || !rightNode.body) {
return ts.isIdentifier(rightNode) ? rightNode : rightNode.name;
}
rightNode = rightNode.body;
}
return typeNameOrNamespaceName;
}
}
@@ -6872,6 +6926,7 @@ namespace ts {
if (canParseTag) {
const child = tryParseChildTag(target);
if (child && child.kind === SyntaxKind.JSDocParameterTag &&
target !== PropertyLikeParse.CallbackParameter &&
(ts.isIdentifier(child.name) || !escapedTextsEqual(name, child.name.left))) {
return false;
}
@@ -6920,12 +6975,12 @@ namespace ts {
case "arg":
case "argument":
case "param":
t = PropertyLikeParse.Parameter;
t = PropertyLikeParse.Parameter | PropertyLikeParse.CallbackParameter;
break;
default:
return false;
}
if (target !== t) {
if (!(target & t)) {
return false;
}
const tag = parseParameterOrPropertyTag(atToken, tagName, target, /*indent*/ undefined);

View File

@@ -413,9 +413,11 @@ namespace ts {
JSDocVariadicType,
JSDocComment,
JSDocTypeLiteral,
JSDocSignature,
JSDocTag,
JSDocAugmentsTag,
JSDocClassTag,
JSDocCallbackTag,
JSDocParameterTag,
JSDocReturnTag,
JSDocTypeTag,
@@ -2053,7 +2055,7 @@ namespace ts {
export type ObjectTypeDeclaration = ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode;
export type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag;
export type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag | JSDocTypedefTag | JSDocCallbackTag | JSDocSignature;
export interface ClassLikeDeclarationBase extends NamedDeclaration, JSDocContainer {
kind: SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression;
@@ -2387,6 +2389,21 @@ namespace ts {
typeExpression?: JSDocTypeExpression | JSDocTypeLiteral;
}
export interface JSDocCallbackTag extends JSDocTag, NamedDeclaration {
parent: JSDoc;
kind: SyntaxKind.JSDocCallbackTag;
fullName?: JSDocNamespaceDeclaration | Identifier;
name?: Identifier;
typeExpression: JSDocSignature;
}
export interface JSDocSignature extends JSDocType, Declaration {
kind: SyntaxKind.JSDocSignature;
typeParameters?: ReadonlyArray<JSDocTemplateTag>;
parameters: ReadonlyArray<JSDocParameterTag>;
type: JSDocReturnTag | undefined;
}
export interface JSDocPropertyLikeTag extends JSDocTag, Declaration {
parent: JSDoc;
name: EntityName;
@@ -4032,7 +4049,7 @@ namespace ts {
}
export interface Signature {
declaration?: SignatureDeclaration; // Originating declaration
declaration?: SignatureDeclaration | JSDocSignature; // Originating declaration
typeParameters?: TypeParameter[]; // Type parameters (undefined if non-generic)
parameters: Symbol[]; // Parameters
/* @internal */

View File

@@ -520,6 +520,9 @@ namespace ts {
case SyntaxKind.SetAccessor:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
case SyntaxKind.JSDocCallbackTag:
case SyntaxKind.JSDocTypedefTag:
case SyntaxKind.JSDocSignature:
return true;
default:
assertTypeIsNever(node);
@@ -561,14 +564,7 @@ namespace ts {
// Gets the nearest enclosing block scope container that has the provided node
// as a descendant, that is not the provided node.
export function getEnclosingBlockScopeContainer(node: Node): Node {
let current = node.parent;
while (current) {
if (isBlockScope(current, current.parent)) {
return current;
}
current = current.parent;
}
return findAncestor(node.parent, current => isBlockScope(current, current.parent));
}
// Return display name of an identifier
@@ -1803,6 +1799,14 @@ namespace ts {
((node as JSDocFunctionType).parameters[0].name as Identifier).escapedText === "new";
}
export function isJSDocTypeAlias(node: Node): node is JSDocTypedefTag | JSDocCallbackTag {
return node.kind === SyntaxKind.JSDocTypedefTag || node.kind === SyntaxKind.JSDocCallbackTag;
}
export function isTypeAlias(node: Node): node is JSDocTypedefTag | JSDocCallbackTag | TypeAliasDeclaration {
return isJSDocTypeAlias(node) || isTypeAliasDeclaration(node);
}
function getSourceOfAssignment(node: Node): Node {
return isExpressionStatement(node) &&
node.expression && isBinaryExpression(node.expression) &&
@@ -1826,6 +1830,8 @@ namespace ts {
return v && v.initializer;
case SyntaxKind.PropertyDeclaration:
return (node as PropertyDeclaration).initializer;
case SyntaxKind.PropertyAssignment:
return (node as PropertyAssignment).initializer;
}
}
@@ -1907,7 +1913,7 @@ namespace ts {
return parameter && parameter.symbol;
}
export function getHostSignatureFromJSDoc(node: JSDocParameterTag): SignatureDeclaration | undefined {
export function getHostSignatureFromJSDoc(node: JSDocTag): SignatureDeclaration | undefined {
const host = getJSDocHost(node);
const decl = getSourceOfDefaultedAssignment(host) ||
getSourceOfAssignment(host) ||
@@ -1918,18 +1924,12 @@ namespace ts {
return decl && isFunctionLike(decl) ? decl : undefined;
}
export function getJSDocHost(node: JSDocTag): HasJSDoc {
while (node.parent.kind === SyntaxKind.JSDocTypeLiteral) {
if (node.parent.parent.kind === SyntaxKind.JSDocTypedefTag) {
node = node.parent.parent as JSDocTypedefTag;
}
else {
// node.parent.parent is a type expression, child of a parameter type
node = node.parent.parent.parent as JSDocParameterTag;
}
export function getJSDocHost(node: Node): HasJSDoc {
const comment = findAncestor(node.parent,
node => !(isJSDocNode(node) || node.flags & NodeFlags.JSDoc) ? "quit" : node.kind === SyntaxKind.JSDocComment);
if (comment) {
return (comment as JSDoc).parent;
}
Debug.assert(node.parent!.kind === SyntaxKind.JSDocComment);
return node.parent!.parent!;
}
export function getTypeParameterFromJsDoc(node: TypeParameterDeclaration & { parent: JSDocTemplateTag }): TypeParameterDeclaration | undefined {
@@ -1938,13 +1938,14 @@ namespace ts {
return find(typeParameters, p => p.name.escapedText === name);
}
export function hasRestParameter(s: SignatureDeclaration): boolean {
const last = lastOrUndefined(s.parameters);
export function hasRestParameter(s: SignatureDeclaration | JSDocSignature): boolean {
const last = lastOrUndefined<ParameterDeclaration | JSDocParameterTag>(s.parameters);
return last && isRestParameter(last);
}
export function isRestParameter(node: ParameterDeclaration): boolean {
return node.dotDotDotToken !== undefined || node.type && node.type.kind === SyntaxKind.JSDocVariadicType;
export function isRestParameter(node: ParameterDeclaration | JSDocParameterTag): boolean {
const type = isJSDocParameterTag(node) ? (node.typeExpression && node.typeExpression.type) : node.type;
return (node as ParameterDeclaration).dotDotDotToken !== undefined || type && type.kind === SyntaxKind.JSDocVariadicType;
}
export const enum AssignmentKind {
@@ -2998,8 +2999,9 @@ namespace ts {
return parameter && parameter.type;
}
export function getThisParameter(signature: SignatureDeclaration): ParameterDeclaration | undefined {
if (signature.parameters.length) {
export function getThisParameter(signature: SignatureDeclaration | JSDocSignature): ParameterDeclaration | undefined {
// callback tags do not currently support this parameters
if (signature.parameters.length && !isJSDocSignature(signature)) {
const thisParameter = signature.parameters[0];
if (parameterIsThisKeyword(thisParameter)) {
return thisParameter;
@@ -3085,7 +3087,10 @@ namespace ts {
* Gets the effective return type annotation of a signature. If the node was parsed in a
* JavaScript file, gets the return type annotation from JSDoc.
*/
export function getEffectiveReturnTypeNode(node: SignatureDeclaration): TypeNode | undefined {
export function getEffectiveReturnTypeNode(node: SignatureDeclaration | JSDocSignature): TypeNode | undefined {
if (isJSDocSignature(node)) {
return node.type && node.type.typeExpression && node.type.typeExpression.type;
}
return node.type || (isInJavaScriptFile(node) ? getJSDocReturnType(node) : undefined);
}
@@ -3093,15 +3098,30 @@ namespace ts {
* Gets the effective type parameters. If the node was parsed in a
* JavaScript file, gets the type parameters from the `@template` tag from JSDoc.
*/
export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters | JSDocTypedefTag) {
return isJSDocTypedefTag(node)
? getJSDocTypeParameterDeclarations(node)
: node.typeParameters || (isInJavaScriptFile(node) ? getJSDocTypeParameterDeclarations(node) : undefined);
export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters) {
if (isJSDocSignature(node)) {
return undefined;
}
if (isJSDocTypeAlias(node)) {
Debug.assert(node.parent.kind === SyntaxKind.JSDocComment);
const templateTags = flatMap(filter(node.parent.tags, isJSDocTemplateTag), tag => tag.typeParameters) as ReadonlyArray<TypeParameterDeclaration>;
const templateTagNodes = templateTags as NodeArray<TypeParameterDeclaration>;
templateTagNodes.pos = templateTagNodes.length > 0 ? first(templateTagNodes).pos : node.pos;
templateTagNodes.end = templateTagNodes.length > 0 ? last(templateTagNodes).end : node.end;
templateTagNodes.hasTrailingComma = false;
return templateTagNodes;
}
return node.typeParameters || (isInJavaScriptFile(node) ? getJSDocTypeParameterDeclarations(node) : undefined);
}
export function getJSDocTypeParameterDeclarations(node: DeclarationWithTypeParameters | JSDocTypedefTag) {
const templateTag = getJSDocTemplateTag(node);
return templateTag && templateTag.typeParameters;
export function getJSDocTypeParameterDeclarations(node: DeclarationWithTypeParameters) {
const tags = filter(getJSDocTags(node), isJSDocTemplateTag);
for (const tag of tags) {
if (!(tag.parent.kind === SyntaxKind.JSDocComment && find(tag.parent.tags, isJSDocTypeAlias))) {
// template tags are only available when a typedef isn't already using them
return tag.typeParameters;
}
}
}
/**
@@ -4638,6 +4658,8 @@ namespace ts {
return undefined;
}
}
case SyntaxKind.JSDocCallbackTag:
return (declaration as JSDocCallbackTag).name;
case SyntaxKind.JSDocTypedefTag:
return getNameOfJSDocTypedef(declaration as JSDocTypedefTag);
case SyntaxKind.ExportAssignment: {
@@ -5475,6 +5497,14 @@ namespace ts {
export function isJSDocTypeLiteral(node: Node): node is JSDocTypeLiteral {
return node.kind === SyntaxKind.JSDocTypeLiteral;
}
export function isJSDocCallbackTag(node: Node): node is JSDocCallbackTag {
return node.kind === SyntaxKind.JSDocCallbackTag;
}
export function isJSDocSignature(node: Node): node is JSDocSignature {
return node.kind === SyntaxKind.JSDocSignature;
}
}
// Node tests
@@ -5636,6 +5666,7 @@ namespace ts {
switch (kind) {
case SyntaxKind.MethodSignature:
case SyntaxKind.CallSignature:
case SyntaxKind.JSDocSignature:
case SyntaxKind.ConstructSignature:
case SyntaxKind.IndexSignature:
case SyntaxKind.FunctionType:
@@ -6104,6 +6135,7 @@ namespace ts {
|| kind === SyntaxKind.TypeParameter
|| kind === SyntaxKind.VariableDeclaration
|| kind === SyntaxKind.JSDocTypedefTag
|| kind === SyntaxKind.JSDocCallbackTag
|| kind === SyntaxKind.JSDocPropertyTag;
}
@@ -6254,7 +6286,7 @@ namespace ts {
/** True if node is of a kind that may contain comment text. */
export function isJSDocCommentContainingNode(node: Node): boolean {
return node.kind === SyntaxKind.JSDocComment || isJSDocTag(node) || isJSDocTypeLiteral(node);
return node.kind === SyntaxKind.JSDocComment || isJSDocTag(node) || isJSDocTypeLiteral(node) || isJSDocSignature(node);
}
// TODO: determine what this does before making it public.