diff --git a/packages/language-service/src/expression_type.ts b/packages/language-service/src/expression_type.ts index 614c91fd4c..fc47f6a875 100644 --- a/packages/language-service/src/expression_type.ts +++ b/packages/language-service/src/expression_type.ts @@ -219,6 +219,7 @@ export class AstType implements AstVisitor { nullable: false, public: true, definition: undefined, + documentation: [], members(): SymbolTable{return _this.scope;}, signatures(): Signature[]{return [];}, selectSignature(types): Signature | undefined{return undefined;}, diff --git a/packages/language-service/src/global_symbols.ts b/packages/language-service/src/global_symbols.ts index 5388916862..2f2c46aba0 100644 --- a/packages/language-service/src/global_symbols.ts +++ b/packages/language-service/src/global_symbols.ts @@ -43,6 +43,10 @@ export const createGlobalSymbolTable: (query: ng.SymbolQuery) => ng.SymbolTable callable: true, definition: undefined, nullable: false, + documentation: [{ + kind: 'text', + text: 'function to cast an expression to the `any` type', + }], members: () => EMPTY_SYMBOL_TABLE, signatures: () => [], selectSignature(args: ng.Symbol[]) { diff --git a/packages/language-service/src/hover.ts b/packages/language-service/src/hover.ts index 04c3a28a80..9234377069 100644 --- a/packages/language-service/src/hover.ts +++ b/packages/language-service/src/hover.ts @@ -8,11 +8,14 @@ import {CompileSummaryKind, StaticSymbol} from '@angular/compiler'; import * as ts from 'typescript'; + import {AstResult} from './common'; import {locateSymbol} from './locate_symbol'; +import * as ng from './types'; import {TypeScriptServiceHost} from './typescript_host'; import {findTightestNode} from './utils'; + // Reverse mappings of enum would generate strings const SYMBOL_SPACE = ts.SymbolDisplayPartKind[ts.SymbolDisplayPartKind.space]; const SYMBOL_PUNC = ts.SymbolDisplayPartKind[ts.SymbolDisplayPartKind.punctuation]; @@ -37,7 +40,7 @@ export function getHover(info: AstResult, position: number, host: Readonly): ts.QuickInfo|undefined { + directive: StaticSymbol, textSpan: ts.TextSpan, host: Readonly, + symbol?: ng.Symbol): ts.QuickInfo|undefined { const analyzedModules = host.getAnalyzedModules(false); const ngModule = analyzedModules.ngModuleByPipeOrDirective.get(directive); if (!ngModule) return; @@ -124,6 +130,7 @@ function getDirectiveModule( kindModifiers: ts.ScriptElementKindModifier.none, // kindModifier info not available on 'ng.Symbol' textSpan, + documentation: symbol ? symbol.documentation : undefined, // This generates a string like '(directive) NgModule.Directive: class' // 'kind' in displayParts does not really matter because it's dropped when // displayParts get converted to string. diff --git a/packages/language-service/src/locate_symbol.ts b/packages/language-service/src/locate_symbol.ts index 63086ed4ca..20e66e4645 100644 --- a/packages/language-service/src/locate_symbol.ts +++ b/packages/language-service/src/locate_symbol.ts @@ -214,6 +214,8 @@ class OverrideKindSymbol implements Symbol { get definition(): Definition { return this.sym.definition; } + get documentation(): ts.SymbolDisplayPart[] { return this.sym.documentation; } + members() { return this.sym.members(); } signatures() { return this.sym.signatures(); } diff --git a/packages/language-service/src/symbols.ts b/packages/language-service/src/symbols.ts index 8a7ea96b3f..2b88deab11 100644 --- a/packages/language-service/src/symbols.ts +++ b/packages/language-service/src/symbols.ts @@ -7,6 +7,8 @@ */ import {StaticSymbol} from '@angular/compiler'; +import * as ts from 'typescript'; + /** * The range of a span of text in a source file. @@ -95,6 +97,11 @@ export interface Symbol { */ readonly nullable: boolean; + /** + * Documentation comment on the Symbol, if any. + */ + readonly documentation: ts.SymbolDisplayPart[]; + /** * A table of the members of the symbol; that is, the members that can appear * after a `.` in an Angular expression. diff --git a/packages/language-service/src/typescript_symbols.ts b/packages/language-service/src/typescript_symbols.ts index d11b1e3e87..834d242b7f 100644 --- a/packages/language-service/src/typescript_symbols.ts +++ b/packages/language-service/src/typescript_symbols.ts @@ -262,6 +262,14 @@ class TypeWrapper implements Symbol { return this.context.checker.getNonNullableType(this.tsType) != this.tsType; } + get documentation(): ts.SymbolDisplayPart[] { + const symbol = this.tsType.getSymbol(); + if (!symbol) { + return []; + } + return symbol.getDocumentationComment(this.context.checker); + } + get definition(): Definition|undefined { const symbol = this.tsType.getSymbol(); return symbol ? definitionFromTsSymbol(symbol) : undefined; @@ -347,6 +355,10 @@ class SymbolWrapper implements Symbol { get definition(): Definition { return definitionFromTsSymbol(this.symbol); } + get documentation(): ts.SymbolDisplayPart[] { + return this.symbol.getDocumentationComment(this.context.checker); + } + members(): SymbolTable { if (!this._members) { if ((this.symbol.flags & (ts.SymbolFlags.Class | ts.SymbolFlags.Interface)) != 0) { @@ -397,9 +409,10 @@ class DeclaredSymbol implements Symbol { get callable(): boolean { return this.declaration.type.callable; } - get definition(): Definition { return this.declaration.definition; } + get documentation(): ts.SymbolDisplayPart[] { return this.declaration.type.documentation; } + members(): SymbolTable { return this.declaration.type.members(); } signatures(): Signature[] { return this.declaration.type.signatures(); } @@ -596,6 +609,14 @@ class PipeSymbol implements Symbol { return symbol ? definitionFromTsSymbol(symbol) : undefined; } + get documentation(): ts.SymbolDisplayPart[] { + const symbol = this.tsType.getSymbol(); + if (!symbol) { + return []; + } + return symbol.getDocumentationComment(this.context.checker); + } + members(): SymbolTable { return EmptyTable.instance; } signatures(): Signature[] { return signaturesOf(this.tsType, this.context); } diff --git a/packages/language-service/test/hover_spec.ts b/packages/language-service/test/hover_spec.ts index 8320dc38ea..2bc04211d5 100644 --- a/packages/language-service/test/hover_spec.ts +++ b/packages/language-service/test/hover_spec.ts @@ -205,6 +205,24 @@ describe('hover', () => { }); expect(toText(displayParts)).toBe('(method) $any: $any'); }); + + it('should provide documentation for a property', () => { + mockHost.override(TEST_TEMPLATE, `
{{~{cursor}title}}
`); + const marker = mockHost.getLocationMarkerFor(TEST_TEMPLATE, 'cursor'); + const quickInfo = ngLS.getHoverAt(TEST_TEMPLATE, marker.start); + expect(quickInfo).toBeDefined(); + const documentation = toText(quickInfo !.documentation); + expect(documentation).toBe('This is the title of the `TemplateReference` Component.'); + }); + + it('should provide documentation for a selector', () => { + mockHost.override(TEST_TEMPLATE, `<~{cursor}test-comp>`); + const marker = mockHost.getLocationMarkerFor(TEST_TEMPLATE, 'cursor'); + const quickInfo = ngLS.getHoverAt(TEST_TEMPLATE, marker.start); + expect(quickInfo).toBeDefined(); + const documentation = toText(quickInfo !.documentation); + expect(documentation).toBe('This Component provides the `test-comp` selector.'); + }); }); function toText(displayParts?: ts.SymbolDisplayPart[]): string { diff --git a/packages/language-service/test/project/app/parsing-cases.ts b/packages/language-service/test/project/app/parsing-cases.ts index 7b2b3fe36c..235982f3d1 100644 --- a/packages/language-service/test/project/app/parsing-cases.ts +++ b/packages/language-service/test/project/app/parsing-cases.ts @@ -132,6 +132,9 @@ export class AsyncForUsingComponent { export class References { } +/** + * This Component provides the `test-comp` selector. + */ /*BeginTestComponent*/ @Component({ selector: 'test-comp', template: '
Testing: {{name}}
', @@ -145,6 +148,9 @@ export class TestComponent { templateUrl: 'test.ng', }) export class TemplateReference { + /** + * This is the title of the `TemplateReference` Component. + */ title = 'Some title'; hero: Hero = {id: 1, name: 'Windstorm'}; heroes: Hero[] = [this.hero];