diff --git a/packages/language-service/BUILD.bazel b/packages/language-service/BUILD.bazel
index 24336820a8..4e1ef571e9 100644
--- a/packages/language-service/BUILD.bazel
+++ b/packages/language-service/BUILD.bazel
@@ -9,8 +9,12 @@ ts_library(
"*.ts",
"src/**/*.ts",
],
+ exclude = [
+ "src/ts_utils.ts",
+ ],
),
deps = [
+ ":ts_utils",
"//packages:types",
"//packages/compiler",
"//packages/compiler-cli",
@@ -20,6 +24,14 @@ ts_library(
],
)
+ts_library(
+ name = "ts_utils",
+ srcs = ["src/ts_utils.ts"],
+ deps = [
+ "@npm//typescript",
+ ],
+)
+
pkg_npm(
name = "npm_package",
srcs = ["package.json"],
diff --git a/packages/language-service/src/definitions.ts b/packages/language-service/src/definitions.ts
index 0164ce2b24..eb10625d7a 100644
--- a/packages/language-service/src/definitions.ts
+++ b/packages/language-service/src/definitions.ts
@@ -10,8 +10,8 @@ import * as path from 'path';
import * as ts from 'typescript'; // used as value and is provided at runtime
import {locateSymbols} from './locate_symbol';
+import {findTightestNode, getClassDeclFromDecoratorProp, getPropertyAssignmentFromValue} from './ts_utils';
import {AstResult, Span} from './types';
-import {findTightestNode, getPropertyAssignmentFromValue, isClassDecoratorProperty} from './utils';
/**
* Convert Angular Span to TypeScript TextSpan. Angular Span has 'start' and
@@ -120,11 +120,11 @@ function getUrlFromProperty(
// `styleUrls`'s property assignment can be found from the array (parent) node.
//
// First search for `templateUrl`.
- let asgn = getPropertyAssignmentFromValue(urlNode);
- if (!asgn || asgn.name.getText() !== 'templateUrl') {
+ let asgn = getPropertyAssignmentFromValue(urlNode, 'templateUrl');
+ if (!asgn) {
// `templateUrl` assignment not found; search for `styleUrls` array assignment.
- asgn = getPropertyAssignmentFromValue(urlNode.parent);
- if (!asgn || asgn.name.getText() !== 'styleUrls') {
+ asgn = getPropertyAssignmentFromValue(urlNode.parent, 'styleUrls');
+ if (!asgn) {
// Nothing found, bail.
return;
}
@@ -132,7 +132,9 @@ function getUrlFromProperty(
// If the property assignment is not a property of a class decorator, don't generate definitions
// for it.
- if (!isClassDecoratorProperty(asgn)) return;
+ if (!getClassDeclFromDecoratorProp(asgn)) {
+ return;
+ }
const sf = urlNode.getSourceFile();
// Extract url path specified by the url node, which is relative to the TypeScript source file
diff --git a/packages/language-service/src/diagnostics.ts b/packages/language-service/src/diagnostics.ts
index f5c55ecd82..e50686677c 100644
--- a/packages/language-service/src/diagnostics.ts
+++ b/packages/language-service/src/diagnostics.ts
@@ -12,9 +12,10 @@ import * as ts from 'typescript';
import {createDiagnostic, Diagnostic} from './diagnostic_messages';
import {getTemplateExpressionDiagnostics} from './expression_diagnostics';
+import {findPropertyValueOfType, findTightestNode} from './ts_utils';
import * as ng from './types';
import {TypeScriptServiceHost} from './typescript_host';
-import {findPropertyValueOfType, findTightestNode, offsetSpan, spanOf} from './utils';
+import {offsetSpan, spanOf} from './utils';
/**
* Return diagnostic information for the parsed AST of the template.
diff --git a/packages/language-service/src/ts_utils.ts b/packages/language-service/src/ts_utils.ts
new file mode 100644
index 0000000000..ea64381317
--- /dev/null
+++ b/packages/language-service/src/ts_utils.ts
@@ -0,0 +1,132 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import * as ts from 'typescript/lib/tsserverlibrary';
+
+/**
+ * Return the node that most tightly encompass the specified `position`.
+ * @param node
+ * @param position
+ */
+export function findTightestNode(node: ts.Node, position: number): ts.Node|undefined {
+ if (node.getStart() <= position && position < node.getEnd()) {
+ return node.forEachChild(c => findTightestNode(c, position)) || node;
+ }
+}
+
+/**
+ * Returns a property assignment from the assignment value if the property name
+ * matches the specified `key`, or `undefined` if there is no match.
+ */
+export function getPropertyAssignmentFromValue(value: ts.Node, key: string): ts.PropertyAssignment|
+ undefined {
+ const propAssignment = value.parent;
+ if (!propAssignment || !ts.isPropertyAssignment(propAssignment) ||
+ propAssignment.name.getText() !== key) {
+ return;
+ }
+ return propAssignment;
+}
+
+/**
+ * Given a decorator property assignment, return the ClassDeclaration node that corresponds to the
+ * directive class the property applies to.
+ * If the property assignment is not on a class decorator, no declaration is returned.
+ *
+ * For example,
+ *
+ * @Component({
+ * template: '
'
+ * ^^^^^^^^^^^^^^^^^^^^^^^---- property assignment
+ * })
+ * class AppComponent {}
+ * ^---- class declaration node
+ *
+ * @param propAsgn property assignment
+ */
+export function getClassDeclFromDecoratorProp(propAsgnNode: ts.PropertyAssignment):
+ ts.ClassDeclaration|undefined {
+ if (!propAsgnNode.parent || !ts.isObjectLiteralExpression(propAsgnNode.parent)) {
+ return;
+ }
+ const objLitExprNode = propAsgnNode.parent;
+ if (!objLitExprNode.parent || !ts.isCallExpression(objLitExprNode.parent)) {
+ return;
+ }
+ const callExprNode = objLitExprNode.parent;
+ if (!callExprNode.parent || !ts.isDecorator(callExprNode.parent)) {
+ return;
+ }
+ const decorator = callExprNode.parent;
+ if (!decorator.parent || !ts.isClassDeclaration(decorator.parent)) {
+ return;
+ }
+ const classDeclNode = decorator.parent;
+ return classDeclNode;
+}
+
+interface DirectiveClassLike {
+ decoratorId: ts.Identifier; // decorator identifier, like @Component
+ classId: ts.Identifier;
+}
+
+/**
+ * Return metadata about `node` if it looks like an Angular directive class.
+ * In this case, potential matches are `@NgModule`, `@Component`, `@Directive`,
+ * `@Pipe`, etc.
+ * These class declarations all share some common attributes, namely their
+ * decorator takes exactly one parameter and the parameter must be an object
+ * literal.
+ *
+ * For example,
+ * v---------- `decoratorId`
+ * @NgModule({ <
+ * declarations: [], < classDecl
+ * }) <
+ * class AppModule {} <
+ * ^----- `classId`
+ *
+ * @param node Potential node that represents an Angular directive.
+ */
+export function getDirectiveClassLike(node: ts.Node): DirectiveClassLike|undefined {
+ if (!ts.isClassDeclaration(node) || !node.name || !node.decorators) {
+ return;
+ }
+ for (const d of node.decorators) {
+ const expr = d.expression;
+ if (!ts.isCallExpression(expr) || expr.arguments.length !== 1 ||
+ !ts.isIdentifier(expr.expression)) {
+ continue;
+ }
+ const arg = expr.arguments[0];
+ if (ts.isObjectLiteralExpression(arg)) {
+ return {
+ decoratorId: expr.expression,
+ classId: node.name,
+ };
+ }
+ }
+}
+
+/**
+ * Finds the value of a property assignment that is nested in a TypeScript node and is of a certain
+ * type T.
+ *
+ * @param startNode node to start searching for nested property assignment from
+ * @param propName property assignment name
+ * @param predicate function to verify that a node is of type T.
+ * @return node property assignment value of type T, or undefined if none is found
+ */
+export function findPropertyValueOfType(
+ startNode: ts.Node, propName: string, predicate: (node: ts.Node) => node is T): T|undefined {
+ if (ts.isPropertyAssignment(startNode) && startNode.name.getText() === propName) {
+ const {initializer} = startNode;
+ if (predicate(initializer)) return initializer;
+ }
+ return startNode.forEachChild(c => findPropertyValueOfType(c, propName, predicate));
+}
diff --git a/packages/language-service/src/typescript_host.ts b/packages/language-service/src/typescript_host.ts
index 13ae098310..ff36ead24e 100644
--- a/packages/language-service/src/typescript_host.ts
+++ b/packages/language-service/src/typescript_host.ts
@@ -13,9 +13,8 @@ import * as tss from 'typescript/lib/tsserverlibrary';
import {createLanguageService} from './language_service';
import {ReflectorHost} from './reflector_host';
import {ExternalTemplate, InlineTemplate} from './template';
+import {findTightestNode, getClassDeclFromDecoratorProp, getDirectiveClassLike, getPropertyAssignmentFromValue} from './ts_utils';
import {AstResult, Declaration, DeclarationError, DiagnosticMessageChain, LanguageService, LanguageServiceHost, Span, TemplateSource} from './types';
-import {findTightestNode, getClassDeclFromDecoratorProp, getDirectiveClassLike, getPropertyAssignmentFromValue} from './utils';
-
/**
* Create a `LanguageServiceHost`
@@ -370,8 +369,8 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
if (!tss.isStringLiteralLike(node)) {
return;
}
- const tmplAsgn = getPropertyAssignmentFromValue(node);
- if (!tmplAsgn || tmplAsgn.name.getText() !== 'template') {
+ const tmplAsgn = getPropertyAssignmentFromValue(node, 'template');
+ if (!tmplAsgn) {
return;
}
const classDecl = getClassDeclFromDecoratorProp(tmplAsgn);
diff --git a/packages/language-service/src/utils.ts b/packages/language-service/src/utils.ts
index a73fac69a6..5969116627 100644
--- a/packages/language-service/src/utils.ts
+++ b/packages/language-service/src/utils.ts
@@ -7,8 +7,6 @@
*/
import {AstPath, BoundEventAst, CompileDirectiveSummary, CompileTypeMetadata, CssSelector, DirectiveAst, ElementAst, EmbeddedTemplateAst, HtmlAstPath, identifierName, Identifiers, Node, ParseSourceSpan, RecursiveTemplateAstVisitor, RecursiveVisitor, TemplateAst, TemplateAstPath, templateVisitAll, visitAll} from '@angular/compiler';
-import * as ts from 'typescript';
-
import {AstResult, DiagnosticTemplateInfo, SelectorInfo, Span, Symbol, SymbolQuery} from './types';
interface SpanHolder {
@@ -146,78 +144,6 @@ export function findTemplateAstAt(ast: TemplateAst[], position: number): Templat
return new AstPath(path, position);
}
-/**
- * Return the node that most tightly encompass the specified `position`.
- * @param node
- * @param position
- */
-export function findTightestNode(node: ts.Node, position: number): ts.Node|undefined {
- if (node.getStart() <= position && position < node.getEnd()) {
- return node.forEachChild(c => findTightestNode(c, position)) || node;
- }
-}
-
-interface DirectiveClassLike {
- decoratorId: ts.Identifier; // decorator identifier, like @Component
- classId: ts.Identifier;
-}
-
-/**
- * Return metadata about `node` if it looks like an Angular directive class.
- * In this case, potential matches are `@NgModule`, `@Component`, `@Directive`,
- * `@Pipe`, etc.
- * These class declarations all share some common attributes, namely their
- * decorator takes exactly one parameter and the parameter must be an object
- * literal.
- *
- * For example,
- * v---------- `decoratorId`
- * @NgModule({ <
- * declarations: [], < classDecl
- * }) <
- * class AppModule {} <
- * ^----- `classId`
- *
- * @param node Potential node that represents an Angular directive.
- */
-export function getDirectiveClassLike(node: ts.Node): DirectiveClassLike|undefined {
- if (!ts.isClassDeclaration(node) || !node.name || !node.decorators) {
- return;
- }
- for (const d of node.decorators) {
- const expr = d.expression;
- if (!ts.isCallExpression(expr) || expr.arguments.length !== 1 ||
- !ts.isIdentifier(expr.expression)) {
- continue;
- }
- const arg = expr.arguments[0];
- if (ts.isObjectLiteralExpression(arg)) {
- return {
- decoratorId: expr.expression,
- classId: node.name,
- };
- }
- }
-}
-
-/**
- * Finds the value of a property assignment that is nested in a TypeScript node and is of a certain
- * type T.
- *
- * @param startNode node to start searching for nested property assignment from
- * @param propName property assignment name
- * @param predicate function to verify that a node is of type T.
- * @return node property assignment value of type T, or undefined if none is found
- */
-export function findPropertyValueOfType(
- startNode: ts.Node, propName: string, predicate: (node: ts.Node) => node is T): T|undefined {
- if (ts.isPropertyAssignment(startNode) && startNode.name.getText() === propName) {
- const {initializer} = startNode;
- if (predicate(initializer)) return initializer;
- }
- return startNode.forEachChild(c => findPropertyValueOfType(c, propName, predicate));
-}
-
/**
* Find the tightest node at the specified `position` from the AST `nodes`, and
* return the path to the node.
@@ -277,62 +203,3 @@ export function findOutputBinding(
}
}
}
-
-/**
- * Returns a property assignment from the assignment value, or `undefined` if there is no
- * assignment.
- */
-export function getPropertyAssignmentFromValue(value: ts.Node): ts.PropertyAssignment|undefined {
- if (!value.parent || !ts.isPropertyAssignment(value.parent)) {
- return;
- }
- return value.parent;
-}
-
-/**
- * Given a decorator property assignment, return the ClassDeclaration node that corresponds to the
- * directive class the property applies to.
- * If the property assignment is not on a class decorator, no declaration is returned.
- *
- * For example,
- *
- * @Component({
- * template: ''
- * ^^^^^^^^^^^^^^^^^^^^^^^---- property assignment
- * })
- * class AppComponent {}
- * ^---- class declaration node
- *
- * @param propAsgn property assignment
- */
-export function getClassDeclFromDecoratorProp(propAsgnNode: ts.PropertyAssignment):
- ts.ClassDeclaration|undefined {
- if (!propAsgnNode.parent || !ts.isObjectLiteralExpression(propAsgnNode.parent)) {
- return;
- }
- const objLitExprNode = propAsgnNode.parent;
- if (!objLitExprNode.parent || !ts.isCallExpression(objLitExprNode.parent)) {
- return;
- }
- const callExprNode = objLitExprNode.parent;
- if (!callExprNode.parent || !ts.isDecorator(callExprNode.parent)) {
- return;
- }
- const decorator = callExprNode.parent;
- if (!decorator.parent || !ts.isClassDeclaration(decorator.parent)) {
- return;
- }
- const classDeclNode = decorator.parent;
- return classDeclNode;
-}
-
-/**
- * Determines if a property assignment is on a class decorator.
- * See `getClassDeclFromDecoratorProperty`, which gets the class the decorator is applied to, for
- * more details.
- *
- * @param prop property assignment
- */
-export function isClassDecoratorProperty(propAsgn: ts.PropertyAssignment): boolean {
- return !!getClassDeclFromDecoratorProp(propAsgn);
-}
diff --git a/packages/language-service/test/BUILD.bazel b/packages/language-service/test/BUILD.bazel
index e3557e87c2..f2f0a5959c 100644
--- a/packages/language-service/test/BUILD.bazel
+++ b/packages/language-service/test/BUILD.bazel
@@ -56,6 +56,7 @@ ts_library(
":test_utils_lib",
"//packages/compiler",
"//packages/language-service",
+ "//packages/language-service:ts_utils",
"@npm//typescript",
],
)
diff --git a/packages/language-service/test/utils_spec.ts b/packages/language-service/test/utils_spec.ts
index c4d2159a99..69cc2b9274 100644
--- a/packages/language-service/test/utils_spec.ts
+++ b/packages/language-service/test/utils_spec.ts
@@ -9,7 +9,8 @@
import * as ng from '@angular/compiler';
import * as ts from 'typescript';
-import {getClassDeclFromDecoratorProp, getDirectiveClassLike, getPathToNodeAtPosition} from '../src/utils';
+import {getClassDeclFromDecoratorProp, getDirectiveClassLike} from '../src/ts_utils';
+import {getPathToNodeAtPosition} from '../src/utils';
import {MockTypescriptHost} from './test_utils';
describe('getDirectiveClassLike', () => {