Skip to content

Commit

Permalink
feat(language-service): Show documentation on hover (#34506)
Browse files Browse the repository at this point in the history
This commit adds dpcumentation to the hover tooltip.

PR closes angular/vscode-ng-language-service#321

PR Close #34506
  • Loading branch information
kyliau authored and alxhub committed Jan 6, 2020
1 parent 2845596 commit 1660095
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 4 deletions.
1 change: 1 addition & 0 deletions packages/language-service/src/expression_type.ts
Expand Up @@ -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;},
Expand Down
4 changes: 4 additions & 0 deletions packages/language-service/src/global_symbols.ts
Expand Up @@ -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[]) {
Expand Down
13 changes: 10 additions & 3 deletions packages/language-service/src/hover.ts
Expand Up @@ -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];
Expand All @@ -37,7 +40,7 @@ export function getHover(info: AstResult, position: number, host: Readonly<TypeS
const textSpan = {start: span.start, length: span.end - span.start};

if (compileTypeSummary && compileTypeSummary.summaryKind === CompileSummaryKind.Directive) {
return getDirectiveModule(compileTypeSummary.type.reference, textSpan, host);
return getDirectiveModule(compileTypeSummary.type.reference, textSpan, host, symbol);
}

const containerDisplayParts: ts.SymbolDisplayPart[] = symbol.container ?
Expand All @@ -57,6 +60,7 @@ export function getHover(info: AstResult, position: number, host: Readonly<TypeS
kind: symbol.kind as ts.ScriptElementKind,
kindModifiers: '', // kindModifier info not available on 'ng.Symbol'
textSpan,
documentation: symbol.documentation,
// this would generate a string like '(property) ClassX.propY: type'
// 'kind' in displayParts does not really matter because it's dropped when
// displayParts get converted to string.
Expand Down Expand Up @@ -105,11 +109,13 @@ export function getTsHover(
/**
* Attempts to get quick info for the NgModule a Directive is declared in.
* @param directive identifier on a potential Directive class declaration
* @param textSpan span of the symbol
* @param host Language Service host to query
* @param symbol the internal symbol that represents the directive
*/
function getDirectiveModule(
directive: StaticSymbol, textSpan: ts.TextSpan,
host: Readonly<TypeScriptServiceHost>): ts.QuickInfo|undefined {
directive: StaticSymbol, textSpan: ts.TextSpan, host: Readonly<TypeScriptServiceHost>,
symbol?: ng.Symbol): ts.QuickInfo|undefined {
const analyzedModules = host.getAnalyzedModules(false);
const ngModule = analyzedModules.ngModuleByPipeOrDirective.get(directive);
if (!ngModule) return;
Expand All @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions packages/language-service/src/locate_symbol.ts
Expand Up @@ -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(); }
Expand Down
7 changes: 7 additions & 0 deletions packages/language-service/src/symbols.ts
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
23 changes: 22 additions & 1 deletion packages/language-service/src/typescript_symbols.ts
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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(); }
Expand Down Expand Up @@ -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); }
Expand Down
18 changes: 18 additions & 0 deletions packages/language-service/test/hover_spec.ts
Expand Up @@ -205,6 +205,24 @@ describe('hover', () => {
});
expect(toText(displayParts)).toBe('(method) $any: $any');
});

it('should provide documentation for a property', () => {
mockHost.override(TEST_TEMPLATE, `<div>{{~{cursor}title}}</div>`);
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></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 {
Expand Down
6 changes: 6 additions & 0 deletions packages/language-service/test/project/app/parsing-cases.ts
Expand Up @@ -132,6 +132,9 @@ export class AsyncForUsingComponent {
export class References {
}

/**
* This Component provides the `test-comp` selector.
*/
/*BeginTestComponent*/ @Component({
selector: 'test-comp',
template: '<div>Testing: {{name}}</div>',
Expand All @@ -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];
Expand Down

0 comments on commit 1660095

Please sign in to comment.