feat(language-service): add definitions for templateUrl (#32238)
Adds support for `getDefinitionAt` when called on a templateUrl property assignment. The currrent architecture for getting definitions is designed to be called on templates, so we have to introduce a new `getTsDefinitionAndBoundSpan` method to get Angular-specific definitions in TypeScript files and pass a `readTemplate` closure that will read the contents of a template using `TypeScriptServiceHost#getTemplates`. We can probably go in and make this nicer in a future PR, though I'm not sure what the best architecture should be yet. Part of angular/vscode-ng-language-service#111 PR Close #32238
This commit is contained in:
@ -6,10 +6,13 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript'; // used as value and is provided at runtime
|
||||
import {AstResult} from './common';
|
||||
import {locateSymbol} from './locate_symbol';
|
||||
import {Span} from './types';
|
||||
import {getPropertyAssignmentFromValue, isClassDecoratorProperty} from './template';
|
||||
import {Span, TemplateSource} from './types';
|
||||
import {findTightestNode} from './utils';
|
||||
|
||||
/**
|
||||
* Convert Angular Span to TypeScript TextSpan. Angular Span has 'start' and
|
||||
@ -59,3 +62,67 @@ export function getDefinitionAndBoundSpan(
|
||||
definitions, textSpan,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an Angular-specific definition in a TypeScript source file.
|
||||
*/
|
||||
export function getTsDefinitionAndBoundSpan(
|
||||
sf: ts.SourceFile, position: number,
|
||||
tsLsHost: Readonly<ts.LanguageServiceHost>): ts.DefinitionInfoAndBoundSpan|undefined {
|
||||
const node = findTightestNode(sf, position);
|
||||
if (!node) return;
|
||||
switch (node.kind) {
|
||||
case ts.SyntaxKind.StringLiteral:
|
||||
case ts.SyntaxKind.NoSubstitutionTemplateLiteral:
|
||||
// Attempt to extract definition of a URL in a property assignment.
|
||||
return getUrlFromProperty(node as ts.StringLiteralLike, tsLsHost);
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to get the definition of a file whose URL is specified in a property assignment in a
|
||||
* directive decorator.
|
||||
* Currently applies to `templateUrl` properties.
|
||||
*/
|
||||
function getUrlFromProperty(
|
||||
urlNode: ts.StringLiteralLike,
|
||||
tsLsHost: Readonly<ts.LanguageServiceHost>): ts.DefinitionInfoAndBoundSpan|undefined {
|
||||
const asgn = getPropertyAssignmentFromValue(urlNode);
|
||||
if (!asgn) return;
|
||||
// If the URL is not a property of a class decorator, don't generate definitions for it.
|
||||
if (!isClassDecoratorProperty(asgn)) return;
|
||||
|
||||
const sf = urlNode.getSourceFile();
|
||||
switch (asgn.name.getText()) {
|
||||
case 'templateUrl':
|
||||
// Extract definition of the template file specified by this `templateUrl` property.
|
||||
const url = path.join(path.dirname(sf.fileName), urlNode.text);
|
||||
|
||||
// If the file does not exist, bail. It is possible that the TypeScript language service host
|
||||
// does not have a `fileExists` method, in which case optimistically assume the file exists.
|
||||
if (tsLsHost.fileExists && !tsLsHost.fileExists(url)) return;
|
||||
|
||||
const templateDefinitions: ts.DefinitionInfo[] = [{
|
||||
kind: ts.ScriptElementKind.externalModuleName,
|
||||
name: url,
|
||||
containerKind: ts.ScriptElementKind.unknown,
|
||||
containerName: '',
|
||||
// Reading the template is expensive, so don't provide a preview.
|
||||
textSpan: {start: 0, length: 0},
|
||||
fileName: url,
|
||||
}];
|
||||
|
||||
return {
|
||||
definitions: templateDefinitions,
|
||||
textSpan: {
|
||||
// Exclude opening and closing quotes in the url span.
|
||||
start: urlNode.getStart() + 1,
|
||||
length: urlNode.getWidth() - 2,
|
||||
},
|
||||
};
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user