diff --git a/packages/language-service/test/BUILD.bazel b/packages/language-service/test/BUILD.bazel
index 4529a90fa3..0312c107d2 100644
--- a/packages/language-service/test/BUILD.bazel
+++ b/packages/language-service/test/BUILD.bazel
@@ -3,7 +3,11 @@ load("//tools:defaults.bzl", "jasmine_node_test", "ts_library")
ts_library(
name = "test_lib",
testonly = True,
- srcs = glob(["**/*.ts"]),
+ srcs = glob(
+ include = ["**/*.ts"],
+ exclude = ["project/**/*"],
+ ),
+ data = glob(["project/**/*"]),
deps = [
"//packages:types",
"//packages/compiler",
diff --git a/packages/language-service/test/definitions_spec.ts b/packages/language-service/test/definitions_spec.ts
index 286b8cf587..f9b9a00ee1 100644
--- a/packages/language-service/test/definitions_spec.ts
+++ b/packages/language-service/test/definitions_spec.ts
@@ -156,7 +156,14 @@ describe('definitions', () => {
expect(def.fileName).toBe(refFileName);
expect(def.name).toBe('TestComponent');
expect(def.kind).toBe('component');
- expect(def.textSpan).toEqual(mockHost.getLocationMarkerFor(refFileName, 'test-comp'));
+ const content = mockHost.getFileContent(refFileName) !;
+ const begin = '/*BeginTestComponent*/ ';
+ const start = content.indexOf(begin) + begin.length;
+ const end = content.indexOf(' /*EndTestComponent*/');
+ expect(def.textSpan).toEqual({
+ start,
+ length: end - start,
+ });
});
it('should be able to find an event provider', () => {
@@ -186,7 +193,12 @@ describe('definitions', () => {
expect(def.fileName).toBe(refFileName);
expect(def.name).toBe('testEvent');
expect(def.kind).toBe('event');
- expect(def.textSpan).toEqual(mockHost.getDefinitionMarkerFor(refFileName, 'test'));
+ const content = mockHost.getFileContent(refFileName) !;
+ const ref = `@Output('test') testEvent = new EventEmitter();`;
+ expect(def.textSpan).toEqual({
+ start: content.indexOf(ref),
+ length: ref.length,
+ });
});
it('should be able to find an input provider', () => {
@@ -219,7 +231,12 @@ describe('definitions', () => {
expect(def.fileName).toBe(refFileName);
expect(def.name).toBe('name');
expect(def.kind).toBe('property');
- expect(def.textSpan).toEqual(mockHost.getDefinitionMarkerFor(refFileName, 'tcName'));
+ const content = mockHost.getFileContent(refFileName) !;
+ const ref = `@Input('tcName') name = 'test';`;
+ expect(def.textSpan).toEqual({
+ start: content.indexOf(ref),
+ length: ref.length,
+ });
});
it('should be able to find a pipe', () => {
diff --git a/packages/language-service/test/project/app/app.component.ts b/packages/language-service/test/project/app/app.component.ts
new file mode 100644
index 0000000000..36a97d5055
--- /dev/null
+++ b/packages/language-service/test/project/app/app.component.ts
@@ -0,0 +1,34 @@
+/**
+ * @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 {Component} from '@angular/core';
+
+export class Hero {
+ id: number;
+ name: string;
+}
+
+@Component({
+ selector: 'my-app',
+ template: `~{empty}
+ <~{start-tag}h~{start-tag-after-h}1~{start-tag-h1} ~{h1-after-space}>
+ ~{h1-content} {{~{sub-start}title~{sub-end}}}
+
+ ~{after-h1}
{{~{h2-hero}hero.~{h2-name}name}} details!
+ id: {{~{label-hero}hero.~{label-id}id}}
+
+ name:
+
+ &~{entity-amp}amp;
+ `
+})
+export class AppComponent {
+ title = 'Tour of Heroes';
+ hero: Hero = {id: 1, name: 'Windstorm'};
+ private internal: string;
+}
diff --git a/packages/language-service/test/project/app/expression-cases.ts b/packages/language-service/test/project/app/expression-cases.ts
new file mode 100644
index 0000000000..7dd2cacf36
--- /dev/null
+++ b/packages/language-service/test/project/app/expression-cases.ts
@@ -0,0 +1,48 @@
+/**
+ * @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 {Component} from '@angular/core';
+
+export interface Person {
+ name: string;
+ age: number;
+}
+
+@Component({
+ template: '{{~{foo}foo~{foo-end}}}',
+})
+export class WrongFieldReference {
+ bar = 'bar';
+}
+
+@Component({
+ template: '{{~{nam}person.nam~{nam-end}}}',
+})
+export class WrongSubFieldReference {
+ person: Person = {name: 'Bob', age: 23};
+}
+
+@Component({
+ template: '{{~{myField}myField~{myField-end}}}',
+})
+export class PrivateReference {
+ private myField = 'My Field';
+}
+
+@Component({
+ template: '{{~{mod}"a" ~{mod-end}% 2}}',
+})
+export class ExpectNumericType {
+}
+
+@Component({
+ template: '{{ (name | lowercase).~{string-pipe}substring }}',
+})
+export class LowercasePipe {
+ name: string;
+}
diff --git a/packages/language-service/test/project/app/main.ts b/packages/language-service/test/project/app/main.ts
new file mode 100644
index 0000000000..1564789f7f
--- /dev/null
+++ b/packages/language-service/test/project/app/main.ts
@@ -0,0 +1,58 @@
+/**
+ * @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 {CommonModule} from '@angular/common';
+import {NgModule} from '@angular/core';
+import {FormsModule} from '@angular/forms';
+
+import {AppComponent} from './app.component';
+import {ExpectNumericType, LowercasePipe, PrivateReference, WrongFieldReference, WrongSubFieldReference} from './expression-cases';
+import {UnknownEven, UnknownPeople, UnknownTrackBy} from './ng-for-cases';
+import {ShowIf} from './ng-if-cases';
+import {AttributeBinding, CaseIncompleteOpen, CaseMissingClosing, CaseUnknown, EmptyInterpolation, EventBinding, ForLetIEqual, ForOfEmpty, ForOfLetEmpty, ForUsingComponent, NoValueAttribute, NumberModel, Pipes, PropertyBinding, References, StringModel, TemplateReference, TestComponent, TwoWayBinding} from './parsing-cases';
+
+@NgModule({
+ imports: [CommonModule, FormsModule],
+ declarations: [
+ AppComponent,
+ CaseIncompleteOpen,
+ CaseMissingClosing,
+ CaseUnknown,
+ Pipes,
+ TemplateReference,
+ NoValueAttribute,
+ AttributeBinding,
+ StringModel,
+ NumberModel,
+ PropertyBinding,
+ EventBinding,
+ TwoWayBinding,
+ EmptyInterpolation,
+ ForOfEmpty,
+ ForOfLetEmpty,
+ ForLetIEqual,
+ ForUsingComponent,
+ References,
+ TestComponent,
+ WrongFieldReference,
+ WrongSubFieldReference,
+ PrivateReference,
+ ExpectNumericType,
+ UnknownPeople,
+ UnknownEven,
+ UnknownTrackBy,
+ ShowIf,
+ LowercasePipe,
+ ]
+})
+export class AppModule {
+}
+
+declare function bootstrap(v: any): void;
+
+ bootstrap(AppComponent);
diff --git a/packages/language-service/test/project/app/ng-for-cases.ts b/packages/language-service/test/project/app/ng-for-cases.ts
new file mode 100644
index 0000000000..1515c2ab2f
--- /dev/null
+++ b/packages/language-service/test/project/app/ng-for-cases.ts
@@ -0,0 +1,43 @@
+/**
+ * @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 {Component} from '@angular/core';
+
+export interface Person {
+ name: string;
+ age: number;
+}
+
+@Component({
+ template: `
+
+ {{person.name}}
+
`,
+})
+export class UnknownPeople {
+}
+
+@Component({
+ template: `
+
+ {{person.name}}
+
`,
+})
+export class UnknownEven {
+ people: Person[];
+}
+
+@Component({
+ template: `
+
+ {{person.name}}
+
`,
+})
+export class UnknownTrackBy {
+ people: Person[];
+}
diff --git a/packages/language-service/test/project/app/ng-if-cases.ts b/packages/language-service/test/project/app/ng-if-cases.ts
new file mode 100644
index 0000000000..9d556235dc
--- /dev/null
+++ b/packages/language-service/test/project/app/ng-if-cases.ts
@@ -0,0 +1,19 @@
+/**
+ * @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 {Component} from '@angular/core';
+
+@Component({
+ template: `
+
+ Showing now!
+
`,
+})
+export class ShowIf {
+ show = false;
+}
diff --git a/packages/language-service/test/project/app/parsing-cases.ts b/packages/language-service/test/project/app/parsing-cases.ts
new file mode 100644
index 0000000000..3fcf215fcd
--- /dev/null
+++ b/packages/language-service/test/project/app/parsing-cases.ts
@@ -0,0 +1,167 @@
+/**
+ * @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 {Component, Directive, EventEmitter, Input, Output} from '@angular/core';
+
+import {Hero} from './app.component';
+
+@Component({
+ template: `
+
+ Some <~{incomplete-open-lt}a~{incomplete-open-a} ~{incomplete-open-attr} text
+ `,
+})
+export class CaseIncompleteOpen {
+}
+
+@Component({
+ template: '',
+})
+export class CaseMissingClosing {
+}
+
+@Component({
+ template: 'Some text ',
+})
+export class CaseUnknown {
+}
+
+@Component({
+ template: '{{data | ~{before-pipe}lowe~{in-pipe}rcase~{after-pipe} }}',
+})
+export class Pipes {
+ data = 'Some string';
+}
+
+@Component({
+ template: ' ',
+})
+export class NoValueAttribute {
+}
+
+
+@Component({
+ template: ' ',
+})
+export class AttributeBinding {
+ test: string;
+}
+
+@Component({
+ template: ' ',
+})
+export class PropertyBinding {
+ test: string;
+}
+
+@Component({
+ template: ' ',
+})
+export class EventBinding {
+ test: string;
+
+ modelChanged() {}
+}
+
+@Component({
+ template: ' ',
+})
+export class TwoWayBinding {
+ test: string;
+}
+
+@Directive({
+ selector: '[string-model]',
+})
+export class StringModel {
+ @Input() model: string;
+ @Output() modelChanged: EventEmitter;
+}
+
+@Directive({
+ selector: '[number-model]',
+})
+export class NumberModel {
+ @Input('inputAlias') model: number;
+ @Output('outputAlias') modelChanged: EventEmitter;
+}
+
+interface Person {
+ name: string;
+ age: number;
+}
+
+@Component({
+ template: '
',
+})
+export class ForOfEmpty {
+}
+
+@Component({
+ template: '
',
+})
+export class ForOfLetEmpty {
+}
+
+@Component({
+ template: '
',
+})
+export class ForLetIEqual {
+}
+
+@Component({
+ template: `
+
+ Name: {{~{for-interp-person}person.~{for-interp-name}name}}
+ Age: {{person.~{for-interp-age}age}}
+
`,
+})
+export class ForUsingComponent {
+ people: Person[];
+}
+
+@Component({
+ template: `
+
+
+ {{~{test-comp-content}}}
+ {{test1.~{test-comp-after-test}name}}
+ {{div.~{test-comp-after-div}.innerText}}
+
+
+ `,
+})
+export class References {
+}
+
+/*BeginTestComponent*/ @Component({
+ selector: 'test-comp',
+ template: 'Testing: {{name}}
',
+})
+export class TestComponent {
+ @Input('tcName') name = 'test';
+ @Output('test') testEvent = new EventEmitter();
+} /*EndTestComponent*/
+
+@Component({
+ templateUrl: 'test.ng',
+})
+export class TemplateReference {
+ title = 'Some title';
+ hero: Hero = {id: 1, name: 'Windstorm'};
+ anyValue: any;
+ myClick(event: any) {}
+}
+
+@Component({
+ template: '{{~{empty-interpolation}}}',
+})
+export class EmptyInterpolation {
+ title = 'Some title';
+ subTitle = 'Some sub title';
+}
diff --git a/packages/language-service/test/project/app/test.css b/packages/language-service/test/project/app/test.css
new file mode 100644
index 0000000000..6af55237a9
--- /dev/null
+++ b/packages/language-service/test/project/app/test.css
@@ -0,0 +1,3 @@
+body, html {
+ width: 100%;
+}
diff --git a/packages/language-service/test/project/app/test.ng b/packages/language-service/test/project/app/test.ng
new file mode 100644
index 0000000000..aec70cabc9
--- /dev/null
+++ b/packages/language-service/test/project/app/test.ng
@@ -0,0 +1,10 @@
+~{empty}
+<~{start-tag}h~{start-tag-after-h}1~{start-tag-h1} ~{h1-after-space}>
+ ~{h1-content} {{~{sub-start}title~{sub-end}}}
+
+~{after-h1}{{~{h2-hero}hero.~{h2-name}name}} details!
+id: {{~{label-hero}hero.~{label-id}id}}
+
+ name:
+
+&~{entity-amp}amp;
diff --git a/packages/language-service/test/project/foo.ts b/packages/language-service/test/project/foo.ts
new file mode 100644
index 0000000000..35fcc18480
--- /dev/null
+++ b/packages/language-service/test/project/foo.ts
@@ -0,0 +1,9 @@
+/**
+ * @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
+ */
+
+export * from './app/app.component';
diff --git a/packages/language-service/test/test_utils.ts b/packages/language-service/test/test_utils.ts
index 53508169a2..5542f3642d 100644
--- a/packages/language-service/test/test_utils.ts
+++ b/packages/language-service/test/test_utils.ts
@@ -27,9 +27,20 @@ const tsxfile = /\.tsx$/;
/* The missing cache does two things. First it improves performance of the
tests as it reduces the number of OS calls made during testing. Also it
- improves debugging experience as fewer exceptions are raised allow you
+ improves debugging experience as fewer exceptions are raised to allow you
to use stopping on all exceptions. */
-const missingCache = new Map();
+const missingCache = new Set([
+ '/node_modules/@angular/core.d.ts',
+ '/node_modules/@angular/animations.d.ts',
+ '/node_modules/@angular/platform-browser/animations.d.ts',
+ '/node_modules/@angular/common.d.ts',
+ '/node_modules/@angular/forms.d.ts',
+ '/node_modules/@angular/core/src/di/provider.metadata.json',
+ '/node_modules/@angular/core/src/change_detection/pipe_transform.metadata.json',
+ '/node_modules/@angular/core/src/reflection/types.metadata.json',
+ '/node_modules/@angular/core/src/reflection/platform_reflection_capabilities.metadata.json',
+ '/node_modules/@angular/forms/src/directives/form_interface.metadata.json',
+]);
const cacheUsed = new Set();
const reportedMissing = new Set();
@@ -39,7 +50,7 @@ const reportedMissing = new Set();
export function validateCache(): {exists: string[], unused: string[], reported: string[]} {
const exists: string[] = [];
const unused: string[] = [];
- for (const fileName of iterableToArray(missingCache.keys())) {
+ for (const fileName of missingCache) {
if (fs.existsSync(fileName)) {
exists.push(fileName);
}
@@ -47,37 +58,72 @@ export function validateCache(): {exists: string[], unused: string[], reported:
unused.push(fileName);
}
}
- return {exists, unused, reported: iterableToArray(reportedMissing.keys())};
+ return {
+ exists,
+ unused,
+ reported: Array.from(reportedMissing),
+ };
}
-missingCache.set('/node_modules/@angular/core.d.ts', true);
-missingCache.set('/node_modules/@angular/animations.d.ts', true);
-missingCache.set('/node_modules/@angular/platform-browser/animations.d.ts', true);
-missingCache.set('/node_modules/@angular/common.d.ts', true);
-missingCache.set('/node_modules/@angular/forms.d.ts', true);
-missingCache.set('/node_modules/@angular/core/src/di/provider.metadata.json', true);
-missingCache.set(
- '/node_modules/@angular/core/src/change_detection/pipe_transform.metadata.json', true);
-missingCache.set('/node_modules/@angular/core/src/reflection/types.metadata.json', true);
-missingCache.set(
- '/node_modules/@angular/core/src/reflection/platform_reflection_capabilities.metadata.json',
- true);
-missingCache.set('/node_modules/@angular/forms/src/directives/form_interface.metadata.json', true);
+function isFile(path: string) {
+ return fs.statSync(path).isFile();
+}
+
+/**
+ * Return a Map with key = directory / file path, value = file content.
+ * [
+ * /app => [[directory]]
+ * /app/main.ts => ...
+ * /app/app.component.ts => ...
+ * /app/expression-cases.ts => ...
+ * /app/ng-for-cases.ts => ...
+ * /app/ng-if-cases.ts => ...
+ * /app/parsing-cases.ts => ...
+ * /app/test.css => ...
+ * /app/test.ng => ...
+ * ]
+ */
+function loadTourOfHeroes(): ReadonlyMap {
+ const {TEST_SRCDIR} = process.env;
+ const root =
+ path.join(TEST_SRCDIR !, 'angular', 'packages', 'language-service', 'test', 'project');
+ const dirs = [root];
+ const files = new Map();
+ while (dirs.length) {
+ const dirPath = dirs.pop() !;
+ for (const filePath of fs.readdirSync(dirPath)) {
+ const absPath = path.join(dirPath, filePath);
+ if (isFile(absPath)) {
+ const key = path.join('/', path.relative(root, absPath));
+ const value = fs.readFileSync(absPath, 'utf8');
+ files.set(key, value);
+ } else {
+ const key = path.join('/', filePath);
+ files.set(key, '[[directory]]');
+ dirs.push(absPath);
+ }
+ }
+ }
+ return files;
+}
+
+const TOH = loadTourOfHeroes();
export class MockTypescriptHost implements ts.LanguageServiceHost {
- private angularPath: string|undefined;
- private nodeModulesPath: string;
- private scriptVersion = new Map();
- private overrides = new Map();
+ private angularPath?: string;
+ private readonly nodeModulesPath: string;
+ private readonly scriptVersion = new Map();
+ private readonly overrides = new Map();
private projectVersion = 0;
private options: ts.CompilerOptions;
- private overrideDirectory = new Set();
- private existsCache = new Map();
- private fileCache = new Map();
+ private readonly overrideDirectory = new Set();
+ private readonly existsCache = new Map();
+ private readonly fileCache = new Map();
constructor(
- private scriptNames: string[], private data: MockData,
- private node_modules: string = 'node_modules', private myPath: typeof path = path) {
+ private readonly scriptNames: string[], _: MockData,
+ private readonly node_modules: string = 'node_modules',
+ private readonly myPath: typeof path = path) {
const support = setup();
this.nodeModulesPath = path.posix.join(support.basePath, 'node_modules');
this.angularPath = path.posix.join(this.nodeModulesPath, '@angular');
@@ -143,14 +189,14 @@ export class MockTypescriptHost implements ts.LanguageServiceHost {
directoryExists(directoryName: string): boolean {
if (this.overrideDirectory.has(directoryName)) return true;
- let effectiveName = this.getEffectiveName(directoryName);
+ const effectiveName = this.getEffectiveName(directoryName);
if (effectiveName === directoryName) {
- return directoryExists(directoryName, this.data);
- } else if (effectiveName == '/' + this.node_modules) {
- return true;
- } else {
- return this.pathExists(effectiveName);
+ return TOH.has(directoryName);
}
+ if (effectiveName === '/' + this.node_modules) {
+ return true;
+ }
+ return this.pathExists(effectiveName);
}
fileExists(fileName: string): boolean { return this.getRawFileContent(fileName) != null; }
@@ -184,29 +230,28 @@ export class MockTypescriptHost implements ts.LanguageServiceHost {
if (/^lib.*\.d\.ts$/.test(basename)) {
let libPath = ts.getDefaultLibFilePath(this.getCompilationSettings());
return fs.readFileSync(this.myPath.join(path.dirname(libPath), basename), 'utf8');
- } else {
- if (missingCache.has(fileName)) {
- cacheUsed.add(fileName);
- return undefined;
- }
+ }
+ if (missingCache.has(fileName)) {
+ cacheUsed.add(fileName);
+ return undefined;
+ }
- const effectiveName = this.getEffectiveName(fileName);
- if (effectiveName === fileName) {
- return open(fileName, this.data);
- } else if (
- !fileName.match(angularts) && !fileName.match(rxjsts) && !fileName.match(rxjsmetadata) &&
- !fileName.match(tsxfile)) {
- if (this.fileCache.has(effectiveName)) {
- return this.fileCache.get(effectiveName);
- } else if (this.pathExists(effectiveName)) {
- const content = fs.readFileSync(effectiveName, 'utf8');
- this.fileCache.set(effectiveName, content);
- return content;
- } else {
- missingCache.set(fileName, true);
- reportedMissing.add(fileName);
- cacheUsed.add(fileName);
- }
+ const effectiveName = this.getEffectiveName(fileName);
+ if (effectiveName === fileName) {
+ return TOH.get(fileName);
+ }
+ if (!fileName.match(angularts) && !fileName.match(rxjsts) && !fileName.match(rxjsmetadata) &&
+ !fileName.match(tsxfile)) {
+ if (this.fileCache.has(effectiveName)) {
+ return this.fileCache.get(effectiveName);
+ } else if (this.pathExists(effectiveName)) {
+ const content = fs.readFileSync(effectiveName, 'utf8');
+ this.fileCache.set(effectiveName, content);
+ return content;
+ } else {
+ missingCache.add(fileName);
+ reportedMissing.add(fileName);
+ cacheUsed.add(fileName);
}
}
}
@@ -313,43 +358,6 @@ export class MockTypescriptHost implements ts.LanguageServiceHost {
}
}
-function iterableToArray(iterator: IterableIterator) {
- const result: T[] = [];
- while (true) {
- const next = iterator.next();
- if (next.done) break;
- result.push(next.value);
- }
- return result;
-}
-
-function find(fileName: string, data: MockData): MockData|undefined {
- let names = fileName.split('/');
- if (names.length && !names[0].length) names.shift();
- let current = data;
- for (let name of names) {
- if (typeof current === 'string')
- return undefined;
- else
- current = (current)[name] !;
- if (!current) return undefined;
- }
- return current;
-}
-
-function open(fileName: string, data: MockData): string|undefined {
- let result = find(fileName, data);
- if (typeof result === 'string') {
- return result;
- }
- return undefined;
-}
-
-function directoryExists(dirname: string, data: MockData): boolean {
- let result = find(dirname, data);
- return !!result && typeof result !== 'string';
-}
-
const locationMarker = /\~\{(\w+(-\w+)*)\}/g;
function removeLocationMarkers(value: string): string {
diff --git a/packages/language-service/test/ts_plugin_spec.ts b/packages/language-service/test/ts_plugin_spec.ts
index 86c4fd5213..c3c8b7f803 100644
--- a/packages/language-service/test/ts_plugin_spec.ts
+++ b/packages/language-service/test/ts_plugin_spec.ts
@@ -36,129 +36,131 @@ describe('plugin', () => {
});
it('should be able to get entity completions',
- () => { contains('app/app.component.ts', 'entity-amp', '&', '>', '<', 'ι'); });
+ () => { contains('/app/app.component.ts', 'entity-amp', '&', '>', '<', 'ι'); });
it('should be able to return html elements', () => {
let htmlTags = ['h1', 'h2', 'div', 'span'];
let locations = ['empty', 'start-tag-h1', 'h1-content', 'start-tag', 'start-tag-after-h'];
for (let location of locations) {
- contains('app/app.component.ts', location, ...htmlTags);
+ contains('/app/app.component.ts', location, ...htmlTags);
}
});
it('should be able to return element directives',
- () => { contains('app/app.component.ts', 'empty', 'my-app'); });
+ () => { contains('/app/app.component.ts', 'empty', 'my-app'); });
- it('should be able to return h1 attributes',
- () => { contains('app/app.component.ts', 'h1-after-space', 'id', 'dir', 'lang', 'onclick'); });
+ it('should be able to return h1 attributes', () => {
+ contains('/app/app.component.ts', 'h1-after-space', 'id', 'dir', 'lang', 'onclick');
+ });
it('should be able to find common angular attributes', () => {
- contains('app/app.component.ts', 'div-attributes', '(click)', '[ngClass]', '*ngIf', '*ngFor');
+ contains('/app/app.component.ts', 'div-attributes', '(click)', '[ngClass]', '*ngIf', '*ngFor');
});
it('should be able to return attribute names with an incompete attribute',
- () => { contains('app/parsing-cases.ts', 'no-value-attribute', 'id', 'dir', 'lang'); });
+ () => { contains('/app/parsing-cases.ts', 'no-value-attribute', 'id', 'dir', 'lang'); });
it('should be able to return attributes of an incomplete element', () => {
- contains('app/parsing-cases.ts', 'incomplete-open-lt', 'a');
- contains('app/parsing-cases.ts', 'incomplete-open-a', 'a');
- contains('app/parsing-cases.ts', 'incomplete-open-attr', 'id', 'dir', 'lang');
+ contains('/app/parsing-cases.ts', 'incomplete-open-lt', 'a');
+ contains('/app/parsing-cases.ts', 'incomplete-open-a', 'a');
+ contains('/app/parsing-cases.ts', 'incomplete-open-attr', 'id', 'dir', 'lang');
});
it('should be able to return completions with a missing closing tag',
- () => { contains('app/parsing-cases.ts', 'missing-closing', 'h1', 'h2'); });
+ () => { contains('/app/parsing-cases.ts', 'missing-closing', 'h1', 'h2'); });
it('should be able to return common attributes of an unknown tag',
- () => { contains('app/parsing-cases.ts', 'unknown-element', 'id', 'dir', 'lang'); });
+ () => { contains('/app/parsing-cases.ts', 'unknown-element', 'id', 'dir', 'lang'); });
it('should be able to get the completions at the beginning of an interpolation',
- () => { contains('app/app.component.ts', 'h2-hero', 'hero', 'title'); });
+ () => { contains('/app/app.component.ts', 'h2-hero', 'hero', 'title'); });
it('should not include private members of a class',
- () => { contains('app/app.component.ts', 'h2-hero', '-internal'); });
+ () => { contains('/app/app.component.ts', 'h2-hero', '-internal'); });
it('should be able to get the completions at the end of an interpolation',
- () => { contains('app/app.component.ts', 'sub-end', 'hero', 'title'); });
+ () => { contains('/app/app.component.ts', 'sub-end', 'hero', 'title'); });
it('should be able to get the completions in a property',
- () => { contains('app/app.component.ts', 'h2-name', 'name', 'id'); });
+ () => { contains('/app/app.component.ts', 'h2-name', 'name', 'id'); });
it('should be able to get a list of pipe values', () => {
- contains('app/parsing-cases.ts', 'before-pipe', 'lowercase', 'uppercase');
- contains('app/parsing-cases.ts', 'in-pipe', 'lowercase', 'uppercase');
- contains('app/parsing-cases.ts', 'after-pipe', 'lowercase', 'uppercase');
+ contains('/app/parsing-cases.ts', 'before-pipe', 'lowercase', 'uppercase');
+ contains('/app/parsing-cases.ts', 'in-pipe', 'lowercase', 'uppercase');
+ contains('/app/parsing-cases.ts', 'after-pipe', 'lowercase', 'uppercase');
});
it('should be able to get completions in an empty interpolation',
- () => { contains('app/parsing-cases.ts', 'empty-interpolation', 'title', 'subTitle'); });
+ () => { contains('/app/parsing-cases.ts', 'empty-interpolation', 'title', 'subTitle'); });
describe('with attributes', () => {
it('should be able to complete property value',
- () => { contains('app/parsing-cases.ts', 'property-binding-model', 'test'); });
+ () => { contains('/app/parsing-cases.ts', 'property-binding-model', 'test'); });
it('should be able to complete an event',
- () => { contains('app/parsing-cases.ts', 'event-binding-model', 'modelChanged'); });
+ () => { contains('/app/parsing-cases.ts', 'event-binding-model', 'modelChanged'); });
it('should be able to complete a two-way binding',
- () => { contains('app/parsing-cases.ts', 'two-way-binding-model', 'test'); });
+ () => { contains('/app/parsing-cases.ts', 'two-way-binding-model', 'test'); });
});
describe('with a *ngFor', () => {
it('should include a let for empty attribute',
- () => { contains('app/parsing-cases.ts', 'for-empty', 'let'); });
+ () => { contains('/app/parsing-cases.ts', 'for-empty', 'let'); });
it('should suggest NgForRow members for let initialization expression', () => {
contains(
- 'app/parsing-cases.ts', 'for-let-i-equal', 'index', 'count', 'first', 'last', 'even',
+ '/app/parsing-cases.ts', 'for-let-i-equal', 'index', 'count', 'first', 'last', 'even',
'odd');
});
- it('should include a let', () => { contains('app/parsing-cases.ts', 'for-let', 'let'); });
- it('should include an "of"', () => { contains('app/parsing-cases.ts', 'for-of', 'of'); });
+ it('should include a let', () => { contains('/app/parsing-cases.ts', 'for-let', 'let'); });
+ it('should include an "of"', () => { contains('/app/parsing-cases.ts', 'for-of', 'of'); });
it('should include field reference',
- () => { contains('app/parsing-cases.ts', 'for-people', 'people'); });
+ () => { contains('/app/parsing-cases.ts', 'for-people', 'people'); });
it('should include person in the let scope',
- () => { contains('app/parsing-cases.ts', 'for-interp-person', 'person'); });
+ () => { contains('/app/parsing-cases.ts', 'for-interp-person', 'person'); });
// TODO: Enable when we can infer the element type of the ngFor
// it('should include determine person\'s type as Person', () => {
- // contains('app/parsing-cases.ts', 'for-interp-name', 'name', 'age');
- // contains('app/parsing-cases.ts', 'for-interp-age', 'name', 'age');
+ // contains('/app/parsing-cases.ts', 'for-interp-name', 'name', 'age');
+ // contains('/app/parsing-cases.ts', 'for-interp-age', 'name', 'age');
// });
});
describe('for pipes', () => {
it('should be able to resolve lowercase',
- () => { contains('app/expression-cases.ts', 'string-pipe', 'substring'); });
+ () => { contains('/app/expression-cases.ts', 'string-pipe', 'substring'); });
});
describe('with references', () => {
it('should list references',
- () => { contains('app/parsing-cases.ts', 'test-comp-content', 'test1', 'test2', 'div'); });
+ () => { contains('/app/parsing-cases.ts', 'test-comp-content', 'test1', 'test2', 'div'); });
it('should reference the component',
- () => { contains('app/parsing-cases.ts', 'test-comp-after-test', 'name'); });
+ () => { contains('/app/parsing-cases.ts', 'test-comp-after-test', 'name'); });
// TODO: Enable when we have a flag that indicates the project targets the DOM
// it('should reference the element if no component', () => {
- // contains('app/parsing-cases.ts', 'test-comp-after-div', 'innerText');
+ // contains('/app/parsing-cases.ts', 'test-comp-after-div', 'innerText');
// });
});
describe('for semantic errors', () => {
it('should report access to an unknown field', () => {
expectSemanticError(
- 'app/expression-cases.ts', 'foo',
+ '/app/expression-cases.ts', 'foo',
'Identifier \'foo\' is not defined. The component declaration, template variable declarations, and element references do not contain such a member');
});
it('should report access to an unknown sub-field', () => {
expectSemanticError(
- 'app/expression-cases.ts', 'nam',
+ '/app/expression-cases.ts', 'nam',
'Identifier \'nam\' is not defined. \'Person\' does not contain such a member');
});
it('should report access to a private member', () => {
expectSemanticError(
- 'app/expression-cases.ts', 'myField',
+ '/app/expression-cases.ts', 'myField',
'Identifier \'myField\' refers to a private member of the component');
});
- it('should report numeric operator errors',
- () => { expectSemanticError('app/expression-cases.ts', 'mod', 'Expected a numeric type'); });
+ it('should report numeric operator errors', () => {
+ expectSemanticError('/app/expression-cases.ts', 'mod', 'Expected a numeric type');
+ });
describe('in ngFor', () => {
function expectError(locationMarker: string, message: string) {
- expectSemanticError('app/ng-for-cases.ts', locationMarker, message);
+ expectSemanticError('/app/ng-for-cases.ts', locationMarker, message);
}
it('should report an unknown field', () => {
expectError(
@@ -176,7 +178,7 @@ describe('plugin', () => {
});
describe('in ngIf', () => {
function expectError(locationMarker: string, message: string) {
- expectSemanticError('app/ng-if-cases.ts', locationMarker, message);
+ expectSemanticError('/app/ng-if-cases.ts', locationMarker, message);
}
it('should report an implicit context reference', () => {
expectError('implicit', 'The template context does not define a member called \'unknown\'');
@@ -197,7 +199,7 @@ describe('plugin', () => {
});
it('should be able to get entity completions', () => {
- const fileName = 'app/app.component.ts';
+ const fileName = '/app/app.component.ts';
const marker = 'entity-amp';
const position = getMarkerLocation(fileName, marker);
const results = ngLS.getCompletionsAtPosition(fileName, position, {} /* options */);
@@ -207,7 +209,7 @@ describe('plugin', () => {
it('should report template diagnostics', () => {
// TODO(kyliau): Rename these to end with '-error.ts'
- const fileName = 'app/expression-cases.ts';
+ const fileName = '/app/expression-cases.ts';
const diagnostics = ngLS.getSemanticDiagnostics(fileName);
expect(diagnostics.map(d => d.messageText)).toEqual([
`Identifier 'foo' is not defined. The component declaration, template variable declarations, and element references do not contain such a member`,
diff --git a/packages/tsconfig.json b/packages/tsconfig.json
index 25db54d1ea..67d052db68 100644
--- a/packages/tsconfig.json
+++ b/packages/tsconfig.json
@@ -44,6 +44,7 @@
// Http doesn't need to built since it is no longer maintained and
// will be removed eventually. See: FW-1392.
"http/**",
+ "language-service/test/project",
"platform-server/integrationtest",
// The webworker packages have deprecated and are not made compatible with the
// strict flag. Until these packages are removed, we exclude them here.