From 5bf6e30f8e839d8e8887ec4ffaa63e70c02e29a4 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 16 May 2018 12:58:36 -0700 Subject: [PATCH 1/2] Use jsdoc aliases if visible when printing types (#24153) * Use jsdoc aliases if visible when printing types * Modify implementation a bit, add test that aughta change in the new future * Accept baselines- hold off on typedef template lookup change --- src/compiler/checker.ts | 10 +- src/compiler/utilities.ts | 8 +- .../checkJsdocTypedefInParamTag1.types | 24 +- tests/baselines/reference/jsDocTypedef1.js | 136 +---------- .../jsdocTemplateConstructorFunction2.types | 2 +- .../reference/jsdocTypedefMissingType.types | 2 +- .../jsdocTypedef_propertyWithNoType.types | 2 +- .../reference/typedefTagNested.types | 4 +- .../jsQuickInfoGenerallyAcceptableSize.ts | 212 ++++++++++++++++++ 9 files changed, 243 insertions(+), 157 deletions(-) create mode 100644 tests/cases/fourslash/jsQuickInfoGenerallyAcceptableSize.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 629e2f1819d..58cbc2f12b7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4024,6 +4024,10 @@ namespace ts { function determineIfDeclarationIsVisible() { switch (node.kind) { + case SyntaxKind.JSDocTypedefTag: + // Top-level jsdoc typedefs 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: return isDeclarationVisible(node.parent.parent); case SyntaxKind.VariableDeclaration: @@ -5163,8 +5167,8 @@ namespace ts { 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) { - const declaration = node; + node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.TypeAliasDeclaration || node.kind === SyntaxKind.JSDocTypedefTag) { + const declaration = node; const typeParameters = getEffectiveTypeParameterDeclarations(declaration); if (typeParameters) { result = appendTypeParameters(result, typeParameters); @@ -9046,7 +9050,7 @@ namespace ts { } function getAliasSymbolForTypeNode(node: TypeNode) { - return node.parent.kind === SyntaxKind.TypeAliasDeclaration ? getSymbolOfNode(node.parent) : undefined; + return (node.parent.kind === SyntaxKind.TypeAliasDeclaration || node.parent.kind === SyntaxKind.JSDocTypedefTag) ? getSymbolOfNode(node.parent) : undefined; } function getAliasTypeArgumentsForTypeNode(node: TypeNode) { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index d5cd22d7b7c..e10d40a70ba 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3093,11 +3093,13 @@ 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) { - return node.typeParameters || (isInJavaScriptFile(node) ? getJSDocTypeParameterDeclarations(node) : undefined); + export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters | JSDocTypedefTag) { + return isJSDocTypedefTag(node) + ? getJSDocTypeParameterDeclarations(node) + : node.typeParameters || (isInJavaScriptFile(node) ? getJSDocTypeParameterDeclarations(node) : undefined); } - export function getJSDocTypeParameterDeclarations(node: DeclarationWithTypeParameters) { + export function getJSDocTypeParameterDeclarations(node: DeclarationWithTypeParameters | JSDocTypedefTag) { const templateTag = getJSDocTemplateTag(node); return templateTag && templateTag.typeParameters; } diff --git a/tests/baselines/reference/checkJsdocTypedefInParamTag1.types b/tests/baselines/reference/checkJsdocTypedefInParamTag1.types index b0847a71522..ffc7fe505e6 100644 --- a/tests/baselines/reference/checkJsdocTypedefInParamTag1.types +++ b/tests/baselines/reference/checkJsdocTypedefInParamTag1.types @@ -10,18 +10,18 @@ * @param {Opts} opts */ function foo(opts) { ->foo : (opts: { x: string; y?: string; z?: string; w?: string; }) => void ->opts : { x: string; y?: string; z?: string; w?: string; } +>foo : (opts: Opts) => void +>opts : Opts opts.x; >opts.x : string ->opts : { x: string; y?: string; z?: string; w?: string; } +>opts : Opts >x : string } foo({x: 'abc'}); >foo({x: 'abc'}) : void ->foo : (opts: { x: string; y?: string; z?: string; w?: string; }) => void +>foo : (opts: Opts) => void >{x: 'abc'} : { x: string; } >x : string >'abc' : "abc" @@ -34,18 +34,18 @@ foo({x: 'abc'}); * @param {AnotherOpts} opts */ function foo1(opts) { ->foo1 : (opts: { anotherX: string; anotherY?: string; }) => void ->opts : { anotherX: string; anotherY?: string; } +>foo1 : (opts: AnotherOpts) => void +>opts : AnotherOpts opts.anotherX; >opts.anotherX : string ->opts : { anotherX: string; anotherY?: string; } +>opts : AnotherOpts >anotherX : string } foo1({anotherX: "world"}); >foo1({anotherX: "world"}) : void ->foo1 : (opts: { anotherX: string; anotherY?: string; }) => void +>foo1 : (opts: AnotherOpts) => void >{anotherX: "world"} : { anotherX: string; } >anotherX : string >"world" : "world" @@ -60,17 +60,17 @@ foo1({anotherX: "world"}); * @param {Opts1} opts */ function foo2(opts) { ->foo2 : (opts: { x: string; y?: string; z?: string; w?: string; }) => void ->opts : { x: string; y?: string; z?: string; w?: string; } +>foo2 : (opts: Opts1) => void +>opts : Opts1 opts.x; >opts.x : string ->opts : { x: string; y?: string; z?: string; w?: string; } +>opts : Opts1 >x : string } foo2({x: 'abc'}); >foo2({x: 'abc'}) : void ->foo2 : (opts: { x: string; y?: string; z?: string; w?: string; }) => void +>foo2 : (opts: Opts1) => void >{x: 'abc'} : { x: string; } >x : string >'abc' : "abc" diff --git a/tests/baselines/reference/jsDocTypedef1.js b/tests/baselines/reference/jsDocTypedef1.js index e1b34f14c2b..bc00dcafb5d 100644 --- a/tests/baselines/reference/jsDocTypedef1.js +++ b/tests/baselines/reference/jsDocTypedef1.js @@ -41,140 +41,8 @@ "kind": "space" }, { - "text": "{", - "kind": "punctuation" - }, - { - "text": "\n", - "kind": "lineBreak" - }, - { - "text": " ", - "kind": "space" - }, - { - "text": "x", - "kind": "propertyName" - }, - { - "text": ":", - "kind": "punctuation" - }, - { - "text": " ", - "kind": "space" - }, - { - "text": "string", - "kind": "keyword" - }, - { - "text": ";", - "kind": "punctuation" - }, - { - "text": "\n", - "kind": "lineBreak" - }, - { - "text": " ", - "kind": "space" - }, - { - "text": "y", - "kind": "propertyName" - }, - { - "text": "?", - "kind": "punctuation" - }, - { - "text": ":", - "kind": "punctuation" - }, - { - "text": " ", - "kind": "space" - }, - { - "text": "string", - "kind": "keyword" - }, - { - "text": ";", - "kind": "punctuation" - }, - { - "text": "\n", - "kind": "lineBreak" - }, - { - "text": " ", - "kind": "space" - }, - { - "text": "z", - "kind": "propertyName" - }, - { - "text": "?", - "kind": "punctuation" - }, - { - "text": ":", - "kind": "punctuation" - }, - { - "text": " ", - "kind": "space" - }, - { - "text": "string", - "kind": "keyword" - }, - { - "text": ";", - "kind": "punctuation" - }, - { - "text": "\n", - "kind": "lineBreak" - }, - { - "text": " ", - "kind": "space" - }, - { - "text": "w", - "kind": "propertyName" - }, - { - "text": "?", - "kind": "punctuation" - }, - { - "text": ":", - "kind": "punctuation" - }, - { - "text": " ", - "kind": "space" - }, - { - "text": "string", - "kind": "keyword" - }, - { - "text": ";", - "kind": "punctuation" - }, - { - "text": "\n", - "kind": "lineBreak" - }, - { - "text": "}", - "kind": "punctuation" + "text": "Opts", + "kind": "aliasName" } ], "documentation": [], diff --git a/tests/baselines/reference/jsdocTemplateConstructorFunction2.types b/tests/baselines/reference/jsdocTemplateConstructorFunction2.types index 5737f8caaf9..787d9ab106b 100644 --- a/tests/baselines/reference/jsdocTemplateConstructorFunction2.types +++ b/tests/baselines/reference/jsdocTemplateConstructorFunction2.types @@ -79,7 +79,7 @@ z.u = false */ /** @type {A} */ const options = { value: null }; ->options : { value: any; } +>options : A >{ value: null } : { value: null; } >value : null >null : null diff --git a/tests/baselines/reference/jsdocTypedefMissingType.types b/tests/baselines/reference/jsdocTypedefMissingType.types index 18cd68abfcb..b161c6ca489 100644 --- a/tests/baselines/reference/jsdocTypedefMissingType.types +++ b/tests/baselines/reference/jsdocTypedefMissingType.types @@ -14,7 +14,7 @@ const t = 0; /** @type Person */ const person = { name: "" }; ->person : { name: string; } +>person : Person >{ name: "" } : { name: string; } >name : string >"" : "" diff --git a/tests/baselines/reference/jsdocTypedef_propertyWithNoType.types b/tests/baselines/reference/jsdocTypedef_propertyWithNoType.types index 7d7326fc80c..553c1b544ae 100644 --- a/tests/baselines/reference/jsdocTypedef_propertyWithNoType.types +++ b/tests/baselines/reference/jsdocTypedef_propertyWithNoType.types @@ -6,7 +6,7 @@ /** @type {Foo} */ const x = { foo: 0 }; ->x : { foo: any; } +>x : Foo >{ foo: 0 } : { foo: number; } >foo : number >0 : 0 diff --git a/tests/baselines/reference/typedefTagNested.types b/tests/baselines/reference/typedefTagNested.types index 6fe8c251205..52707d55cec 100644 --- a/tests/baselines/reference/typedefTagNested.types +++ b/tests/baselines/reference/typedefTagNested.types @@ -10,7 +10,7 @@ var ex; /** @type {App} */ const app = { ->app : { name: string; icons: { image32: string; image64: string; }; } +>app : App >{ name: 'name', icons: { image32: 'x.png', image64: 'y.png', }} : { name: string; icons: { image32: string; image64: string; }; } name: 'name', @@ -40,5 +40,5 @@ const app = { /** @type {Opp} */ var mistake; ->mistake : { name: string; oops: { horrible: string; }; } +>mistake : Opp diff --git a/tests/cases/fourslash/jsQuickInfoGenerallyAcceptableSize.ts b/tests/cases/fourslash/jsQuickInfoGenerallyAcceptableSize.ts new file mode 100644 index 00000000000..a6fa8059481 --- /dev/null +++ b/tests/cases/fourslash/jsQuickInfoGenerallyAcceptableSize.ts @@ -0,0 +1,212 @@ +/// + +// @allowJs: true +// @checkJs: true +// @Filename: index.js +////// Data table +/////** +//// @typedef DataTableThing +//// @type {Thing} +//// @property {function(TagCollection, Location, string, string, Infotable):void} AddDataTableEntries - (arg0: tags, arg1: location, arg2: source, arg3: sourceType, arg4: values) Add multiple data table entries. +//// @property {function(TagCollection, Location, string, string, Infotable):string} AddDataTableEntry - (arg0: tags, arg1: location, arg2: source, arg3: sourceType, arg4: values) Add a new data table entry. +//// @property {function(TagCollection, Location, string, string, Infotable):void} AddOrUpdateDataTableEntries - (arg0: tags, arg1: location, arg2: source, arg3: sourceType, arg4: values) Add or update multiple data table entries. +//// @property {function(TagCollection, Location, string, string, Infotable):string} AddOrUpdateDataTableEntry - (arg0: tags, arg1: location, arg2: source, arg3: sourceType, arg4: values) Add a new data table entry, or if it exists, update an existing entry. +//// @property {function(TagCollection, Location, string, string, Infotable):void} AssignDataTableEntries - (arg0: tags, arg1: location, arg2: source, arg3: sourceType, arg4: values) Replaces existing data table entries. +//// @property {function():Infotable} CreateValues - Create an empty info table of the correct datashape for this data table. +//// @property {function(*):Infotable} CreateValuesWithData - (arg0: values as JSONObject) Create an info table of the correct datashape for this stream and include data values. +//// @property {function(Infotable):void} DeleteDataTableEntries - (arg0: values as Infotable) Delete all table entries that match the provided values. +//// @property {function(TagCollection, Location, string, string, Infotable, *):void} DeleteDataTableEntriesWithQuery - (arg0: tags, arg1: location, arg2: source, arg3: sourceType, arg4: values, arg5: query as JSONObject) Delete multiple data table entries based on a query. +//// @property {function(Infotable):void} DeleteDataTableEntry - (arg0: values as Infotable) Delete an existing data table entry +//// @property {function(string):void} DeleteDataTableEntryByKey - (arg0: key) Delete an existing data table entry using its key value. +//// @property {function(Infotable):Infotable} FindDataTableEntries - (arg0: values as Infotable) Retrieve all table entries that match the provided values. +//// @property {function():DataShapeDefinition} getDataShape +//// @property {function():string} GetDataShape - Get the currently assigned data shape. +//// @property {function():string} getDataShapeName +//// @property {function(number):Infotable} GetDataTableEntries - (arg0: maxItems) Retrieve all table entries up to max items number. +//// @property {function(Infotable):Infotable} GetDataTableEntry - (arg0: values as Infotable) Get an existing data table entry. +//// @property {function(string):Infotable} GetDataTableEntryByKey - (arg0: key) Get an existing data table entry using its key value. +//// @property {function():number} GetDataTableEntryCount - Get an count of data table entries. +//// @property {function():ThingworxRelationshipTypes} getDataType +//// @property {function():EntityReferenceTypeMap} getDependencies +//// @property {function():IDataEntryCloseableIterator} getEntryIterator - Returns an iterator over all entries inside this data table thing. +//// @property {function():Infotable} GetFieldNames - Retrieve a list of field names for the data shape associated with this stream. +//// @property {function(string):Infotable} GetFieldNamesByType - (arg0: key) Retrieve a list of field names for the data shape associated with this stream, of a specific type. +//// @property {function():string} getItemCollectionName +//// @property {function():string} getItemEntityName +//// @property {function():ThingworxRelationshipTypes} getItemEntityType +//// @property {function():void} initializeEntity +//// @property {function():void} initializeThing +//// @property {function():boolean} isStoredAsEncrypted +//// @property {function():void} PurgeDataTableEntries - Remove all data table entries. +//// @property {function(Infotable, number, TagCollection, string, *):Infotable} QueryDataTableEntries - (arg0: values, arg1: maxItems, arg2: tags, arg3: source, arg4: query as JSONObject) Retrieve all table entries that match the query parameters. +//// @property {function():void} Reindex - Reindex the custom indexes on the data table. +//// @property {function(number, string, TagCollection, *, string):Infotable} SearchDataTableEntries - (arg0: maxItems, arg1: searchExpression, arg2: tags, arg3: query as JSONObject, arg4: source) Retrieve all table entries that match the search query parameters. +//// @property {function(string):void} SetDataShape - (arg0: name) Sets the data shape. +//// @property {function(TagCollection, Location, string, string, Infotable):void} UpdateDataTableEntries - (arg0: tags, arg1: location, arg2: source, arg3: sourceType, arg4: values) Update multiple data table entries. +//// @property {function(TagCollection, Location, string, string, Infotable, *, Infotable):void} UpdateDataTableEntriesWithQuery - (arg0: tags, arg1: location, arg2: source, arg3: sourceType, arg4: values, arg5: query as JSONObject, arg6: updatValues) Add or update multiple data table entries based on a query. +//// @property {function(TagCollection, Location, string, string, Infotable):void} UpdateDataTableEntry - (arg0: tags, arg1: location, arg2: source, arg3: sourceType, arg4: values) update an existing data table entry. +//// @property {function(ImportedEntityCollection):void} validateConfiguration - (arg0: importedEntityCollections) +////*/ +//// +/////** +//// @typedef Infotable +//// @type {object} +//// @property {boolean} isCompressed +//// @property {DataShape} dataShape +//// @property {function(FieldDefinition):int} addField - Adds a field to the DataShapeDefinition of this InfoTable, given a FieldDefinition +//// @property {function(*):void} AddField - *FROM SNIPPET* adds a new field definition to the datashape (arg0 is an object that should match with datashape) +//// @property {function(object):void} AddRow - *FROM SNIPPET* adds a row to the infotable (arg0 is an object that should match with datashape) +//// @property {function(ValueCollection):int} addRow - Adds a row to this InfoTable's ValueCollectionList given a ValueCollection +//// @property {ValueCollectionList} rows - returns the ValueCollectionList of the rows in this InfoTable +//// @property {function(Infotable, boolean):int} addRowsFrom - Adds the rows from an InfoTable to this InfoTable given: the InfoTable to be copied from and a Boolean indicating whether the copied values should be references or cloned values. (arg 0: infotable, arg1: clone) +//// @property {function():Infotable} clone +//// @property {function():Infotable} cloneStructure +//// @property {function():ValueCollection} currentRow - Returns the current row in this InfoTable as a ValueCollection +//// @property {function(object):void} Delete - *FROM SNIPPET* delete rows by value filter (arg0 is an object that should match with datashape) +//// @property {function(IFilter):int} delete +//// @property {function(ValueCollection):int} delete - Creates an AndFilterCollection based on the given ValueCollection and deletes all rows falling within the parameters of that filter +//// @property {function(IFilter):Infotable} deleteRowsToNewTable +//// @property {function(object):void} Filter - *FROM SNIPPET* filters the infotable (arg0 is an object that should match with datashape) +//// @property {function(ValueCollection):void} filter - Creates an AndFilterCollection based on the given ValueCollection and applies it to this InfoTable +//// @property {function(IFilter):void} filterRows +//// @property {function(IFilter):Infotable} filterRowsToNewTable +//// @property {function(*):Infotable} FilterToNewTable - Finds rows in this InfoTable with values that match the values given as a JSONObject and returns them as a new InfoTable +//// @property {function(object):void} Find - *FROM SNIPPET* retrieve rows by value filter (arg0 is an object that should match with datashape) +//// @property {function(IFilter):ValueCollection} find - Finds and returns a row from this InfoTable that falls within the parameters of the given IFilter +//// @property {function(ValueCollection):ValueCollection} find - Finds and returns a row from this InfoTable that matches the values of all fields given as a (ValueCollection) +//// @property {function(ValueCollection, string[]):ValueCollection} find - Finds and returns a row in this InfoTable given the fields to search as a String Array and the values to match as a ( ValueCollection) +//// @property {function(ValueCollection):int} findIndex - Finds and returns the index of a row from this InfoTable that matches the values of all fields given as a ( ValueCollection) +//// @property {function(*):Infotable} fromJSON +//// @property {function():DataShapeDefinition} getDataShape - Returns the DataShapeDefinition for this InfoTable +//// @property {function(string):FieldDefinition} getField - Returns a FieldDefinition from this InfoTable's DataShapeDefinition, given the name of the field as a String +//// @property {function():int} getFieldCount - Returns the number of fields in this InfoTable's DataShape as an int +//// @property {function():ValueCollection} getFirstRow - Returns the first row (ValueCollection) of this InfoTable +//// @property {function():InfoTableRowIndex} getIndex - +//// @property {function():ValueCollection} getLastRow - Returns the last row in this InfoTable as a ValueCollection +//// @property {function():number} getLength - Returns the number of rows in this InfoTable as an Integer +//// @property {function():DataShapeDefinition} getPublicDataShape - Returns a DataShapeDefinition for this InfoTable containing only the public fields +//// @property {function():*} getReturnValue - Returns the first value of the first field in the first row of this InfoTable +//// @property {function(number):ValueCollection} getRow - *FROM SNIPPET* retrieves a row by index +//// @property {function():number} getRowCount - *FROM SNIPPET* gets the count of rows +//// @property {function():ValueCollectionList} getRows - Returns a ValueCollectionList of the rows in this InfoTable +//// @property {function(string):IPrimitiveType} getRowValue - Returns a value as an IPrimitiveType from the first row of this InfoTable, given a field name as a String +//// @property {function(string):boolean} hasField - Verifies a field exists in this InfoTable's DataShape given the field name as a String +//// @property {function(string[],boolean):void} indexOn +//// @property {function(string, boolean):void} indexOn +//// @property {function():boolean} isEmpty - Returns a boolean indicating whether this InfoTable has a size of zero +//// @property {function(BaseTypes):boolean} isType +//// @property {function():void} moveToFirst - Moves to the first row of this InfoTable. +//// @property {function():ValueCollection} nextRow - Returns the row after the current row in this InfoTable as a ValueCollection +//// @property {function(string):void} quickSort - (arg0: fieldName) +//// @property {function(string, boolean):void} quickSort - (arg0: fieldName, arg1: isAscending) +//// @property {function():void} RemoveAllRows - *FROM SNIPPET* remove all rows from infotable +//// @property {function():void} removeAllRows - remove all rows from infotable +//// @property {function(string):void} RemoveField - *FROM SNIPPET* remove a datashape field by name +//// @property {function(number):void} RemoveRow - *FROM SNIPPET* removes a row by index +//// @property {function(number):void} removeRow - Removes a ValueCollection from the InfoTable given the row as an int +//// @property {function(DataShapeDefinition):void} setDataShape - Sets DataShapeDefinition for this InfoTable +//// @property {function():void} setRow - Sets a single row in this InfoTable given a ValueCollection as a row and the index of the row to be replaced +//// @property {function(ValueCollectionList):void} setRows - Sets the rows in this InfoTable given a ValueCollectionList +//// @property {function(Sort):void} Sort - *FROM SNIPPET* sorts the table +//// @property {function(ISort):void} sortRows +//// @property {function():Infotable} sortRowsToNewTable +//// @property {function():*} toJSON +//// @property {function():JsonInfotable} ToJSON - *FROM SNIPPET* returns the table as JsonInfotable +//// @property {function(number):void} topN - (arg0: maxItems) +//// @property {function(number):Infotable} topNToNewTable - (arg0: maxItems) +//// @property {function(IFIlter, ValueCollection):Infotable} updateRowsToNewTable - (arg0: filters, arg1: values) +////*/ +//// +/////** +//// @typedef DataShapeDefinition +//// @type {object} +//// @property {function(FieldDefinition):void} addFieldDefinition - Adds a new field definition to this data shape definition. +//// @property {function():DataShapeDefinition} clone - Creates a deep clone of this data shape definition +//// @property {function():FieldDefinition} getFieldDefinition - Returns the field definition with the specified name. +//// @property {function():FieldDefinitionCollection} getFields - Returns the collection of field definitions belonging to this data shape definition. +//// @property {function():boolean} hasField - Tests if the field named exists in this definition. +//// @property {function():boolean} hasPrimaryKey - Tests if this definition contains any fields that are designated as primary keys. +//// @property {function():boolean} matches - Determines if this data shape definition has the same fields with the same base types as the provided data shape definition. +//// @property {function():void} setFields - Replaces the fields belonging to this data shape definition with the fields provided in the specified collection. +//// @property {function():*} toJSON - Serializes this data shape definition into JSON format. +////*/ +//// +/////** +//// @typedef FieldDefinition +//// @type {object} +//// @property {function(AspectCollection):boolean} aspectsMatch - Determines whether or not the aspects assigned to this field are equivalent to the aspects in the provided collection. +//// @property {function():FieldDefinition} clone - Creates a deep clone of this field definition. +//// @property {function():AspectCollection} getAspects - Returns the collection of aspects belonging to this field. +//// @property {function():BaseTypes} getBaseType - Returns the base type assigned to this field. +//// @property {function():string} getDataShapeName - Returns the data shape name assigned to the ASPECT_DATASHAPE aspect, if the base type for this field is set to INFOTABLE. +//// @property {function():IPrimitiveType} getDefaultValue - Returns the default value assigned to this field, if one has been defined according to the ASPECT_DEFAULTVALUE aspect. +//// @property {function():number} getOrdinal - Returns the ordinal value assigned to this field. +//// @property {function():boolean} hasDataShape - Determines if, when the base type of this field is an INFOTABLE, a data shape has been assigned. +//// @property {function():boolean} hasDefaultValue - Determines if this field has a default value according to the ASPECT_DEFAULTVALUE aspect. +//// @property {function():boolean} isDataTableEntry - Determines if, when the base type of this field is an INFOTABLE, the contents of the info table will be derived from a data table entry. +//// @property {function():boolean} isPrimaryKey - Determines if this field has the ASPECT_ISPRIMARYKEY aspect set to true. +//// @property {function():boolean} isPrivate - Determines if this field has the ASPECT_ISPRIVATE aspect set to true. +//// @property {function():boolean} isRequired - Determines if this field has the ASPECT_ISREQUIRED aspect set to true. +//// @property {function():boolean} isStreamEntry - Determines if, when the base type of this field is an INFOTABLE, the contents of the info table will be derived from a stream entry. +//// @property {function(AspectCollection):void} setAspects - Replaces all aspects on this field with the aspects in the specified collection. +//// @property {function(BaseTypes):void} setBaseType - Assigns the specified base type to this field. +//// @property {function(number):void} setOrdinal - Sets the ordinal value for this field. +////*/ +//// +/////** +//// @typedef ValueCollectionList +//// @type {ArrayList} +//// @property {function():Infotable} convertToTypedInfoTable +//// @property {function():ValueCollection} currentRow +//// @property {function(ValueCollection):ValueCollection} find - arg0: values +//// @property {function(ValueCollection, string[]):ValueCollection} find - arg0: values, arg1: columns +//// @property {function(ValueCollection):number} findIndex +//// @property {function():ValueCollection} getFirstRow +//// @property {function():ValueCollection} getLastRow +//// @property {function():number} getLength +//// @property {function(number):ValueCollection} getRow - arg0: index +//// @property {function():number} getRowCount +//// @property {function(string):IPrimitiveType} getRowValue - arg0: name +//// @property {function():void} moveToFirst +//// @property {function():ValueCollection} nextRow +////*/ +//// +/////** +//// @typedef ValueCollection +//// @type {NamedObject} +//// @property {function():ValueCollection} clone +//// @property {function(*,DataShapeDefinition):ValueCollection} fromJSONTyped +//// @property {function(string):*} getJSONSerializedValue +//// @property {function(string):IPrimitiveType} getPrimitive +//// @property {function(string):string} getStringValue +//// @property {function(string):*} getValue +//// @property {function(string):boolean} has +//// @property {function(ValueCollection):boolean} matches +//// @property {function():Infotable} toInfoTable +//// @property {function():*} toJSON +//// @property {function(DataShapeDefinition):*} toJSONTyped +//// @property {function():NamedValueCollection} toNamedValueCollection +////*/ +//// +//// +/////** +//// * Do something +//// * @param {DataTableThing} dataTable +//// */ +////var doSome/*1*/thing = function (dataTable) { +////}; +//// +/////** +//// * @callback SomeCallback +//// * @param {number} foo +//// * @param {string} bar +//// */ +//// +//// /** +//// * Another thing +//// * @type {SomeCallback} +//// */ +////var another/*2*/Thing = function(a, b) {} + +verify.quickInfoAt("1", "var doSomething: (dataTable: DataTableThing) => void", "Do something"); +verify.quickInfoAt("2", "var anotherThing: any", "Another thing"); // TODO: #23947 From e01c7d23e11d20ffcce35a2415b9d9f6280f7a33 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 16 May 2018 13:09:54 -0700 Subject: [PATCH 2/2] Correctly show instantiated signatures for JSX element signature help and quick info (#23492) * Correctly show instantiated signatures for JSX element signature help * Also bundle fix for quickinfo * Use more complete cache to avoid duplicate errors --- src/compiler/checker.ts | 105 +++++++++++------- src/compiler/types.ts | 1 + tests/cases/fourslash/jsxGenericQuickInfo.tsx | 22 ++++ ...ParametershasInstantiatedSignatureHelp.tsx | 19 ++++ 4 files changed, 105 insertions(+), 42 deletions(-) create mode 100644 tests/cases/fourslash/jsxGenericQuickInfo.tsx create mode 100644 tests/cases/fourslash/jsxWithTypeParametershasInstantiatedSignatureHelp.tsx diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 58cbc2f12b7..086f581e153 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -401,6 +401,7 @@ namespace ts { const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, unknownType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); const resolvingSignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); const silentNeverSignature = createSignature(undefined, undefined, undefined, emptyArray, silentNeverType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); + const resolvingSignaturesArray = [resolvingSignature]; const enumNumberIndexInfo = createIndexInfo(stringType, /*isReadonly*/ true); const jsObjectLiteralIndexInfo = createIndexInfo(anyType, /*isReadonly*/ false); @@ -15359,8 +15360,17 @@ namespace ts { } } - if (context.typeArguments) { - signatures = mapDefined(signatures, s => getJsxSignatureTypeArgumentInstantiation(s, context, isJs)); + const links = getNodeLinks(context); + if (!links.resolvedSignatures) { + links.resolvedSignatures = createMap(); + } + const cacheKey = "" + getTypeId(valueType); + if (links.resolvedSignatures.get(cacheKey) && links.resolvedSignatures.get(cacheKey) !== resolvingSignaturesArray) { + signatures = links.resolvedSignatures.get(cacheKey); + } + else if (!links.resolvedSignatures.get(cacheKey)) { + links.resolvedSignatures.set(cacheKey, resolvingSignaturesArray); + links.resolvedSignatures.set(cacheKey, signatures = instantiateJsxSignatures(context, signatures)); } return getUnionType(map(signatures, ctor ? t => getJsxPropsTypeFromClassType(t, isJs, context, /*reportErrors*/ false) : t => getJsxPropsTypeFromCallSignature(t, context)), UnionReduction.None); @@ -16341,6 +16351,40 @@ namespace ts { return undefined; } + function getInstantiatedJsxSignatures(openingLikeElement: JsxOpeningLikeElement, elementType: Type, reportErrors?: boolean) { + const links = getNodeLinks(openingLikeElement); + if (!links.resolvedSignatures) { + links.resolvedSignatures = createMap(); + } + const cacheKey = "" + getTypeId(elementType); + if (links.resolvedSignatures.get(cacheKey) && links.resolvedSignatures.get(cacheKey) === resolvingSignaturesArray) { + return; + } + else if (links.resolvedSignatures.get(cacheKey)) { + return links.resolvedSignatures.get(cacheKey); + } + + links.resolvedSignatures.set(cacheKey, resolvingSignaturesArray); + // Resolve the signatures, preferring constructor + let signatures = getSignaturesOfType(elementType, SignatureKind.Construct); + if (signatures.length === 0) { + // No construct signatures, try call signatures + signatures = getSignaturesOfType(elementType, SignatureKind.Call); + if (signatures.length === 0) { + // We found no signatures at all, which is an error + if (reportErrors) { + error(openingLikeElement.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(openingLikeElement.tagName)); + } + return; + } + } + + // Instantiate in context of source type + const results = instantiateJsxSignatures(openingLikeElement, signatures); + links.resolvedSignatures.set(cacheKey, results); + return results; + } + /** * Resolve attributes type of the given opening-like element. The attributes type is a type of attributes associated with the given elementType. * For instance: @@ -16403,20 +16447,10 @@ namespace ts { // Get the element instance type (the result of newing or invoking this tag) - // Resolve the signatures, preferring constructor - let signatures = getSignaturesOfType(elementType, SignatureKind.Construct); - if (signatures.length === 0) { - // No construct signatures, try call signatures - signatures = getSignaturesOfType(elementType, SignatureKind.Call); - if (signatures.length === 0) { - // We found no signatures at all, which is an error - error(openingLikeElement.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(openingLikeElement.tagName)); - return unknownType; - } + const instantiatedSignatures = getInstantiatedJsxSignatures(openingLikeElement, elementType, /*reportErrors*/ true); + if (!length(instantiatedSignatures)) { + return unknownType; } - - // Instantiate in context of source type - const instantiatedSignatures = instantiateJsxSignatures(openingLikeElement, signatures); const elemInstanceType = getUnionType(map(instantiatedSignatures, getReturnTypeOfSignature), UnionReduction.Subtype); // If we should include all stateless attributes type, then get all attributes type from all stateless function signature. @@ -18106,11 +18140,11 @@ namespace ts { let typeArguments: NodeArray; - if (!isDecorator && !isJsxOpeningOrSelfClosingElement) { + if (!isDecorator) { typeArguments = (node).typeArguments; // We already perform checking on the type arguments on the class declaration itself. - if (isTaggedTemplate || (node).expression.kind !== SyntaxKind.SuperKeyword) { + if (isTaggedTemplate || isJsxOpeningOrSelfClosingElement || (node).expression.kind !== SyntaxKind.SuperKeyword) { forEach(typeArguments, checkSourceElement); } } @@ -18699,30 +18733,6 @@ namespace ts { */ function getResolvedJsxStatelessFunctionSignature(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[]): Signature | undefined { Debug.assert(!(elementType.flags & TypeFlags.Union)); - return resolveStatelessJsxOpeningLikeElement(openingLikeElement, elementType, candidatesOutArray); - } - - /** - * Try treating a given opening-like element as stateless function component and resolve a tagName to a function signature. - * @param openingLikeElement an JSX opening-like element we want to try resolve its stateless function if possible - * @param elementType a type of the opening-like JSX element, a result of resolving tagName in opening-like element. - * @param candidatesOutArray an array of signature to be filled in by the function. It is passed by signature help in the language service; - * the function will fill it up with appropriate candidate signatures - * @return a resolved signature if we can find function matching function signature through resolve call or a first signature in the list of functions. - * otherwise return undefined if tag-name of the opening-like element doesn't have call signatures - */ - function resolveStatelessJsxOpeningLikeElement(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[]): Signature | undefined { - // If this function is called from language service, elementType can be a union type. This is not possible if the function is called from compiler (see: resolveCustomJsxElementAttributesType) - if (elementType.flags & TypeFlags.Union) { - const types = (elementType as UnionType).types; - let result: Signature; - for (const type of types) { - result = result || resolveStatelessJsxOpeningLikeElement(openingLikeElement, type, candidatesOutArray); - } - - return result; - } - const callSignatures = elementType && getSignaturesOfType(elementType, SignatureKind.Call); if (callSignatures && callSignatures.length > 0) { return resolveCall(openingLikeElement, callSignatures, candidatesOutArray); @@ -18744,7 +18754,18 @@ namespace ts { case SyntaxKind.JsxOpeningElement: case SyntaxKind.JsxSelfClosingElement: // This code-path is called by language service - return resolveStatelessJsxOpeningLikeElement(node, checkExpression(node.tagName), candidatesOutArray) || unknownSignature; + const exprTypes = checkExpression(node.tagName); + return forEachType(exprTypes, exprType => { + const sfcResult = getResolvedJsxStatelessFunctionSignature(node, exprType, candidatesOutArray); + if (sfcResult && sfcResult !== unknownSignature) { + return sfcResult; + } + const sigs = getInstantiatedJsxSignatures(node, exprType); + if (candidatesOutArray && length(sigs)) { + candidatesOutArray.push(...sigs); + } + return length(sigs) ? sigs[0] : unknownSignature; + }) || unknownSignature; } Debug.assertNever(node, "Branch in 'resolveSignature' should be unreachable."); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 5c1069538d0..f2a6f001a21 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3625,6 +3625,7 @@ namespace ts { flags?: NodeCheckFlags; // Set of flags specific to Node resolvedType?: Type; // Cached type of type node resolvedSignature?: Signature; // Cached signature of signature node or call expression + resolvedSignatures?: Map; // Cached signatures of jsx node resolvedSymbol?: Symbol; // Cached name resolution result resolvedIndexInfo?: IndexInfo; // Cached indexing info resolution result maybeTypePredicate?: boolean; // Cached check whether call expression might reference a type predicate diff --git a/tests/cases/fourslash/jsxGenericQuickInfo.tsx b/tests/cases/fourslash/jsxGenericQuickInfo.tsx new file mode 100644 index 00000000000..898c7c17790 --- /dev/null +++ b/tests/cases/fourslash/jsxGenericQuickInfo.tsx @@ -0,0 +1,22 @@ +/// +//@Filename: file.tsx +//// declare module JSX { +//// interface Element { } +//// interface IntrinsicElements { +//// } +//// interface ElementAttributesProperty { props } +//// } +//// interface Props { +//// items: T[]; +//// renderItem: (item: T) => string; +//// } +//// class Component { +//// constructor(props: Props) {} +//// props: Props; +//// } +//// var b = new Component({items: [0, 1, 2], render/*0*/Item: it/*1*/em => item.toFixed()}); +//// var c = item.toFixed()} +verify.quickInfoAt("0", "(property) Props.renderItem: (item: number) => string"); +verify.quickInfoAt("1", "(parameter) item: number"); +verify.quickInfoAt("2", "(JSX attribute) renderItem: (item: number) => string"); +verify.quickInfoAt("3", "(parameter) item: number"); diff --git a/tests/cases/fourslash/jsxWithTypeParametershasInstantiatedSignatureHelp.tsx b/tests/cases/fourslash/jsxWithTypeParametershasInstantiatedSignatureHelp.tsx new file mode 100644 index 00000000000..e317fe338ed --- /dev/null +++ b/tests/cases/fourslash/jsxWithTypeParametershasInstantiatedSignatureHelp.tsx @@ -0,0 +1,19 @@ +/// + +//// declare namespace JSX { +//// interface Element { +//// render(): Element | string | false; +//// } +//// } +//// +//// function SFC(_props: Record) { +//// return ''; +//// } +//// +//// (); +//// (/>); + +goTo.marker("1"); +verify.currentSignatureHelpIs("SFC(_props: Record): string"); +goTo.marker("2"); +verify.currentSignatureHelpIs("SFC(_props: Record): string");