diff --git a/src/angular/ng2Walker.ts b/src/angular/ng2Walker.ts
index c90294ad6..daca97b9c 100644
--- a/src/angular/ng2Walker.ts
+++ b/src/angular/ng2Walker.ts
@@ -32,6 +32,7 @@ export interface Ng2WalkerConfig {
expressionVisitorCtrl?: RecursiveAngularExpressionVisitorCtr;
templateVisitorCtrl?: TemplateAstVisitorCtr;
cssVisitorCtrl?: CssAstVisitorCtrl;
+ languageService?: ts.LanguageService;
}
export class Ng2Walker extends Lint.RuleWalker {
@@ -41,11 +42,6 @@ export class Ng2Walker extends Lint.RuleWalker {
protected _metadataReader?: MetadataReader) {
super(sourceFile, _originalOptions);
this._metadataReader = this._metadataReader || ng2WalkerFactoryUtils.defaultMetadataReader();
- this._config = Object.assign({
- templateVisitorCtrl: BasicTemplateAstVisitor,
- expressionVisitorCtrl: RecursiveAngularExpressionVisitor,
- cssVisitorCtrl: BasicCssAstVisitor
- }, this._config || {});
this._config = Object.assign({
templateVisitorCtrl: BasicTemplateAstVisitor,
@@ -169,7 +165,8 @@ export class Ng2Walker extends Lint.RuleWalker {
const referenceVisitor = new ReferenceCollectorVisitor();
const visitor =
new this._config.templateVisitorCtrl(
- sourceFile, this._originalOptions, context, baseStart, this._config.expressionVisitorCtrl);
+ sourceFile, this._originalOptions, context, baseStart, this._config.expressionVisitorCtrl,
+ this._config.languageService);
compiler.templateVisitAll(referenceVisitor, roots, null);
visitor._variables = referenceVisitor.variables;
compiler.templateVisitAll(visitor, roots, context.controller);
diff --git a/src/angular/templates/basicTemplateAstVisitor.ts b/src/angular/templates/basicTemplateAstVisitor.ts
index d5e475d7f..12e79a90c 100644
--- a/src/angular/templates/basicTemplateAstVisitor.ts
+++ b/src/angular/templates/basicTemplateAstVisitor.ts
@@ -64,13 +64,14 @@ const getExpressionDisplacement = (binding: any) => {
export interface RecursiveAngularExpressionVisitorCtr {
- new(sourceFile: ts.SourceFile, options: Lint.IOptions, context: ComponentMetadata, basePosition: number);
+ new(sourceFile: ts.SourceFile, options: Lint.IOptions, context: ComponentMetadata, basePosition: number,
+ languageService?: ts.LanguageService);
}
export interface TemplateAstVisitorCtr {
new(sourceFile: ts.SourceFile, options: Lint.IOptions, context: ComponentMetadata,
- templateStart: number, expressionVisitorCtrl: RecursiveAngularExpressionVisitorCtr);
+ templateStart: number, expressionVisitorCtrl: RecursiveAngularExpressionVisitorCtr, languageService?: ts.LanguageService);
}
export class BasicTemplateAstVisitor extends SourceMappingVisitor implements ast.TemplateAstVisitor {
@@ -80,13 +81,14 @@ export class BasicTemplateAstVisitor extends SourceMappingVisitor implements ast
private _originalOptions: Lint.IOptions,
protected context: ComponentMetadata,
protected templateStart: number,
- private expressionVisitorCtrl: RecursiveAngularExpressionVisitorCtr = RecursiveAngularExpressionVisitor) {
+ private expressionVisitorCtrl: RecursiveAngularExpressionVisitorCtr = RecursiveAngularExpressionVisitor,
+ protected languageService?: ts.LanguageService) {
super(sourceFile, _originalOptions, context.template.template, templateStart);
}
protected visitNg2TemplateAST(ast: e.AST, templateStart: number) {
const templateVisitor =
- new this.expressionVisitorCtrl(this.getSourceFile(), this._originalOptions, this.context, templateStart);
+ new this.expressionVisitorCtrl(this.getSourceFile(), this._originalOptions, this.context, templateStart, this.languageService);
templateVisitor.preDefinedVariables = this._variables;
templateVisitor.visit(ast);
templateVisitor.getFailures().forEach(f => this.addFailure(f));
diff --git a/src/angular/templates/recursiveAngularExpressionVisitor.ts b/src/angular/templates/recursiveAngularExpressionVisitor.ts
index 702eea9de..818153eb6 100644
--- a/src/angular/templates/recursiveAngularExpressionVisitor.ts
+++ b/src/angular/templates/recursiveAngularExpressionVisitor.ts
@@ -9,7 +9,7 @@ export class RecursiveAngularExpressionVisitor extends SourceMappingVisitor impl
public preDefinedVariables = [];
constructor(sourceFile: ts.SourceFile, options: Lint.IOptions,
- protected context: ComponentMetadata, protected basePosition: number) {
+ protected context: ComponentMetadata, protected basePosition: number, protected languageService: ts.LanguageService) {
super(sourceFile, options, context.template.template, basePosition);
}
diff --git a/src/noAccessMissingMemberRule.ts b/src/noAccessMissingMemberRule.ts
index 8e0a6d0d4..df6796b3f 100644
--- a/src/noAccessMissingMemberRule.ts
+++ b/src/noAccessMissingMemberRule.ts
@@ -5,7 +5,7 @@ import {stringDistance} from './util/utils';
import {Ng2Walker} from './angular/ng2Walker';
import {RecursiveAngularExpressionVisitor} from './angular/templates/recursiveAngularExpressionVisitor';
import {ExpTypes} from './angular/expressionTypes';
-import {getDeclaredMethodNames, getDeclaredPropertyNames} from './util/classDeclarationUtils';
+import {getClassMembers} from './util/classDeclarationUtils';
import * as e from '@angular/compiler/src/expression_parser/ast';
import {Config} from './angular/config';
@@ -44,8 +44,10 @@ class SymbolAccessValidator extends RecursiveAngularExpressionVisitor {
symbolType = 'property';
}
- available = getDeclaredMethodNames(this.context.controller)
- .concat(getDeclaredPropertyNames(this.context.controller))
+ const typeChecker = this.languageService.getProgram().getTypeChecker();
+
+ available = getClassMembers(this.context.controller, typeChecker)
+ .map(p => p.name)
.concat(this.preDefinedVariables);
// Do not support nested properties yet
@@ -122,14 +124,24 @@ class SymbolAccessValidator extends RecursiveAngularExpressionVisitor {
}
}
-export class Rule extends Lint.Rules.AbstractRule {
- static FAILURE: string = 'The %s "%s" that you\'re trying to access does not exist in the class declaration.';
+export class Rule extends Lint.Rules.TypedRule {
+ public static FAILURE: string = 'The %s "%s" that you\'re trying to access does not exist in the class declaration.';
+ public static metadata: Lint.IRuleMetadata = {
+ ruleName: 'no-access-missing-member',
+ description: 'Prevents bindings to expressions containing non-existing methods or properties',
+ optionsDescription: 'Not configurable',
+ options: null,
+ type: 'functionality',
+ typescriptOnly: true
+ };
- public apply(sourceFile:ts.SourceFile): Lint.RuleFailure[] {
+ public applyWithProgram(sourceFile: ts.SourceFile, languageService: ts.LanguageService): Lint.RuleFailure[] {
+ const sf = languageService.getProgram().getSourceFiles().filter(sf => sf.fileName === sourceFile.fileName).pop();
return this.applyWithWalker(
- new Ng2Walker(sourceFile,
+ new Ng2Walker(sf,
this.getOptions(), {
- expressionVisitorCtrl: SymbolAccessValidator
+ expressionVisitorCtrl: SymbolAccessValidator,
+ languageService
}));
}
}
diff --git a/src/util/classDeclarationUtils.ts b/src/util/classDeclarationUtils.ts
index 0e690878f..66ca5af3c 100644
--- a/src/util/classDeclarationUtils.ts
+++ b/src/util/classDeclarationUtils.ts
@@ -3,6 +3,11 @@ import { current } from './syntaxKind';
const SyntaxKind = current();
+export const getClassMembers = (declaration: ts.ClassDeclaration, tc: ts.TypeChecker): ts.Symbol[] => {
+ const properties = tc.getTypeAtLocation(declaration).getProperties();
+ return properties;
+};
+
export const getDeclaredProperties = (declaration: ts.ClassDeclaration) => {
const m = declaration.members;
const ctr = m.filter((m: any) => m.kind === SyntaxKind.Constructor).pop();
diff --git a/test/noAccessMissingMemberRule.spec.ts b/test/noAccessMissingMemberRule.spec.ts
index 53b958fc8..cbced578b 100644
--- a/test/noAccessMissingMemberRule.spec.ts
+++ b/test/noAccessMissingMemberRule.spec.ts
@@ -1,25 +1,26 @@
-import {assertFailure, assertSuccess} from './testHelper';
+import { assertFailure, assertSuccess } from './testHelper';
import {Config} from '../src/angular/config';
describe('no-access-missing-member', () => {
describe('invalid expressions', () => {
it('should fail when interpolating missing property', () => {
let source = `
+
@Component({
selector: 'foobar',
- template: '
{{ foo }}
+ template: '
{{ foo }}
'
})
- class Test {
+ export class Test {
bar: number;
}`;
assertFailure('no-access-missing-member', source, {
message: 'The property "foo" that you\'re trying to access does not exist in the class declaration.',
startPosition: {
- line: 3,
+ line: 4,
character: 29
},
endPosition: {
- line: 3,
+ line: 4,
character: 32
}
});
@@ -27,19 +28,20 @@ describe('no-access-missing-member', () => {
it('should work with existing properties and pipes', () => {
let source = `
+
@Component({
selector: 'foobar',
template: \`\`
})
- class Test {}`;
+ export class Test {}`;
assertFailure('no-access-missing-member', source, {
message: 'The property "showMenu" that you\'re trying to access does not exist in the class declaration.',
startPosition: {
- line: 3,
+ line: 4,
character: 40
},
endPosition: {
- line: 3,
+ line: 4,
character: 48
}
});
@@ -47,21 +49,22 @@ describe('no-access-missing-member', () => {
it('should fail when using missing method', () => {
let source = `
+
@Component({
selector: 'foobar',
- template: '
{{ baz() }}
+ template: '
{{ baz() }}
'
})
- class Test {
+ export class Test {
bar() {}
}`;
assertFailure('no-access-missing-member', source, {
message: 'The method "baz" that you\'re trying to access does not exist in the class declaration. Probably you mean: "bar".',
startPosition: {
- line: 3,
+ line: 4,
character: 29
},
endPosition: {
- line: 3,
+ line: 4,
character: 32
}
});
@@ -69,21 +72,22 @@ describe('no-access-missing-member', () => {
it('should fail when using missing method in an interpolation mixed with text', () => {
let source = `
+
@Component({
selector: 'foobar',
- template: '
test {{ baz() }}
+ template: '
test {{ baz() }}
'
})
- class Test {
+ export class Test {
bar() {}
}`;
assertFailure('no-access-missing-member', source, {
message: 'The method "baz" that you\'re trying to access does not exist in the class declaration. Probably you mean: "bar".',
startPosition: {
- line: 3,
+ line: 4,
character: 35
},
endPosition: {
- line: 3,
+ line: 4,
character: 38
}
});
@@ -91,21 +95,22 @@ describe('no-access-missing-member', () => {
it('should fail when using missing method in an interpolation mixed with text and interpolation', () => {
let source = `
+
@Component({
selector: 'foobar',
- template: '
test {{ bar() }} {{ baz() }}
+ template: '
test {{ bar() }} {{ baz() }}
'
})
- class Test {
+ export class Test {
bar() {}
}`;
assertFailure('no-access-missing-member', source, {
message: 'The method "baz" that you\'re trying to access does not exist in the class declaration. Probably you mean: "bar".',
startPosition: {
- line: 3,
+ line: 4,
character: 47
},
endPosition: {
- line: 3,
+ line: 4,
character: 50
}
});
@@ -113,21 +118,22 @@ describe('no-access-missing-member', () => {
it('should fail when using missing method in an interpolation mixed with text, interpolation & binary expression', () => {
let source = `
+
@Component({
selector: 'foobar',
- template: '
test {{ bar() }} {{ bar() + baz() }}
+ template: '
test {{ bar() }} {{ bar() + baz() }}
'
})
- class Test {
+ export class Test {
bar() {}
}`;
assertFailure('no-access-missing-member', source, {
message: 'The method "baz" that you\'re trying to access does not exist in the class declaration. Probably you mean: "bar".',
startPosition: {
- line: 3,
+ line: 4,
character: 55
},
endPosition: {
- line: 3,
+ line: 4,
character: 58
}
});
@@ -135,21 +141,22 @@ describe('no-access-missing-member', () => {
it('should fail in binary operation with missing property', () => {
let source = `
+
@Component({
selector: 'foobar',
- template: '
{{ baz2() + foo }}
+ template: '
{{ baz2() + foo }}
'
})
- class Test {
+ export class Test {
baz2() {}
}`;
assertFailure('no-access-missing-member', source, {
message: 'The property "foo" that you\'re trying to access does not exist in the class declaration.',
startPosition: {
- line: 3,
+ line: 4,
character: 38
},
endPosition: {
- line: 3,
+ line: 4,
character: 41
}
});
@@ -157,22 +164,23 @@ describe('no-access-missing-member', () => {
it('should fail fail in binary operation with missing method', () => {
let source = `
+
@Component({
selector: 'foobar',
- template: '
{{ baz() + getPrsonName(1, 2, 3) }}
+ template: '
{{ baz() + getPrsonName(1, 2, 3) }}
'
})
- class Test {
+ export class Test {
baz() {}
getPersonName() {}
}`;
assertFailure('no-access-missing-member', source, {
message: 'The method "getPrsonName" that you\'re trying to access does not exist in the class declaration. Probably you mean: "getPersonName".',
startPosition: {
- line: 3,
+ line: 4,
character: 37
},
endPosition: {
- line: 3,
+ line: 4,
character: 49
}
});
@@ -180,21 +188,22 @@ describe('no-access-missing-member', () => {
it('should fail with property binding and missing method', () => {
let source = `
+
@Component({
selector: 'foobar',
- template: '
+ template: ''
})
- class Test {
+ export class Test {
baz() {}
}`;
assertFailure('no-access-missing-member', source, {
message: 'The method "bar" that you\'re trying to access does not exist in the class declaration. Probably you mean: "baz".',
startPosition: {
- line: 3,
+ line: 4,
character: 39
},
endPosition: {
- line: 3,
+ line: 4,
character: 42
}
});
@@ -202,21 +211,22 @@ describe('no-access-missing-member', () => {
it('should fail with style binding and missing method', () => {
let source = `
+
@Component({
selector: 'foobar',
- template: '
+ template: ''
})
- class Test {
+ export class Test {
baz() {}
}`;
assertFailure('no-access-missing-member', source, {
message: 'The method "bar" that you\'re trying to access does not exist in the class declaration. Probably you mean: "baz".',
startPosition: {
- line: 3,
+ line: 4,
character: 41
},
endPosition: {
- line: 3,
+ line: 4,
character: 44
}
});
@@ -224,21 +234,22 @@ describe('no-access-missing-member', () => {
it('should fail on event handling with missing method', () => {
let source = `
+
@Component({
selector: 'foobar',
- template: '
+ template: ''
})
- class Test {
+ export class Test {
baz() {}
}`;
assertFailure('no-access-missing-member', source, {
message: 'The method "bar" that you\'re trying to access does not exist in the class declaration. Probably you mean: "baz".',
startPosition: {
- line: 3,
+ line: 4,
character: 35
},
endPosition: {
- line: 3,
+ line: 4,
character: 38
}
});
@@ -246,21 +257,22 @@ describe('no-access-missing-member', () => {
it('should fail on event handling on the right position with a lot of whitespace', () => {
let source = `
+
@Component({
selector: 'foobar',
- template: '
+ template: ''
})
- class Test {
+ export class Test {
baz() {}
}`;
assertFailure('no-access-missing-member', source, {
message: 'The method "bar" that you\'re trying to access does not exist in the class declaration. Probably you mean: "baz".',
startPosition: {
- line: 3,
+ line: 4,
character: 41
},
endPosition: {
- line: 3,
+ line: 4,
character: 44
}
});
@@ -268,22 +280,23 @@ describe('no-access-missing-member', () => {
it('should fail on event handling on the right position with spaces and newlines', () => {
let source = `
+
@Component({
selector: 'foobar',
template: \`\`
})
- class Test {
+ export class Test {
baz() {}
}`;
assertFailure('no-access-missing-member', source, {
message: 'The method "bar" that you\'re trying to access does not exist in the class declaration. Probably you mean: "baz".',
startPosition: {
- line: 4,
+ line: 5,
character: 12
},
endPosition: {
- line: 4,
+ line: 5,
character: 15
}
});
@@ -292,11 +305,12 @@ describe('no-access-missing-member', () => {
it('should not throw when template ref used outside component scope', () => {
let source = `
+
@Component({
selector: 'foobar',
template: ''
})
- class Test {
+ export class Test {
foo: number;
}`;
assertSuccess('no-access-missing-member', source);
@@ -304,22 +318,24 @@ describe('no-access-missing-member', () => {
it('should not throw when routerLinkActive template ref is used in component', () => {
let source = `
+
@Component({
selector: 'foobar',
template: '{{ test }}'
})
- class Test {}`;
+ export class Test {}`;
assertSuccess('no-access-missing-member', source);
});
it('should not throw when ngModel template ref is used in component', () => {
let source = `
+
@Component({
selector: 'foobar',
template: '{{ test }}'
})
- class Test {
+ export class Test {
foo: string;
}`;
assertSuccess('no-access-missing-member', source);
@@ -327,11 +343,12 @@ describe('no-access-missing-member', () => {
it('should not throw when [md-menu-item] template ref is used in component', () => {
let source = `
+
@Component({
selector: 'foobar',
template: '
{{ test }}
'
})
- class Test {
+ export class Test {
foo: string;
}`;
assertSuccess('no-access-missing-member', source);
@@ -339,11 +356,12 @@ describe('no-access-missing-member', () => {
it('should not throw when md-menu template ref is used in component', () => {
let source = `
+
@Component({
selector: 'foobar',
template: '{{ test }}'
})
- class Test {
+ export class Test {
foo: string;
}`;
assertSuccess('no-access-missing-member', source);
@@ -351,61 +369,66 @@ describe('no-access-missing-member', () => {
it('should not throw when md-button-toggle-group template ref is used in component', () => {
let source = `
+
@Component({
selector: 'foobar',
template: '{{ test }}'
})
- class Test {}`;
+ export class Test {}`;
assertSuccess('no-access-missing-member', source);
});
it('should not throw when md-menu-trigger-for template ref is used in component', () => {
let source = `
+
@Component({
selector: 'foobar',
template: '
{{ test }}
'
})
- class Test {}`;
+ export class Test {}`;
assertSuccess('no-access-missing-member', source);
});
it('should not throw when mdTooltip template ref is used in component', () => {
let source = `
+
@Component({
selector: 'foobar',
template: '
{{ test }}
'
})
- class Test {}`;
+ export class Test {}`;
assertSuccess('no-access-missing-member', source);
});
it('should not throw when mdSelect template ref is used in component', () => {
let source = `
+
@Component({
selector: 'foobar',
template: '{{ test }}'
})
- class Test {}`;
+ export class Test {}`;
assertSuccess('no-access-missing-member', source);
});
it('should fail with missing ref', () => {
let source = `
+
@Component({
selector: 'foobar',
template: ''
})
- class Test {
+ export class Test {
foo: number;
}`;
assertFailure('no-access-missing-member', source, {
message: 'The property "todoForm" that you\'re trying to access does not exist in the class declaration.',
startPosition: {
- line: 3,
+ line: 4,
character: 63
},
endPosition: {
- line: 3,
+ line: 4,
character: 71
}
});
@@ -413,11 +436,12 @@ describe('no-access-missing-member', () => {
it('should succeed with elementref', () => {
let source = `
+
@Component({
selector: 'foobar',
template: '{{ baz.value }}'
})
- class Test {
+ export class Test {
foo: number;
}`;
assertSuccess('no-access-missing-member', source);
@@ -428,11 +452,12 @@ describe('no-access-missing-member', () => {
describe('valid expressions', () => {
it('should succeed with "ngForm" ref', () => {
let source = `
+
@Component({
selector: 'foobar',
template: ''
})
- class Test {
+ export class Test {
foo: number;
}`;
assertSuccess('no-access-missing-member', source);
@@ -440,11 +465,12 @@ describe('no-access-missing-member', () => {
it('should support custom template refs', () => {
let source = `
+
@Component({
selector: 'foobar',
template: ''
})
- class Test {
+ export class Test {
foo: number;
}`;
Config.predefinedDirectives.push({
@@ -457,11 +483,12 @@ describe('no-access-missing-member', () => {
it('should succeed with inline property declaration', () => {
let source = `
+
@Component({
selector: 'foobar',
template: '{{ foo }}'
})
- class Test {
+ export class Test {
constructor(public foo: number) {}
}`;
assertSuccess('no-access-missing-member', source);
@@ -469,11 +496,12 @@ describe('no-access-missing-member', () => {
it('should succeed with declared property', () => {
let source = `
+
@Component({
selector: 'foobar',
template: '
{{ foo }}
'
})
- class Test {
+ export class Test {
foo: number;
}`;
assertSuccess('no-access-missing-member', source);
@@ -481,11 +509,12 @@ describe('no-access-missing-member', () => {
it('should succeed on declared method', () => {
let source = `
+
@Component({
selector: 'foobar',
- template: '
{{ foo() }}
+ template: '
{{ foo() }}
'
})
- class Test {
+ export class Test {
foo() {}
}`;
assertSuccess('no-access-missing-member', source);
@@ -495,11 +524,12 @@ describe('no-access-missing-member', () => {
describe('nested properties and pipes', () => {
it('should work with existing single-level nested properties', () => {
let source = `
+
@Component({
selector: 'foobar',
- template: '
{{ foo.bar }}
+ template: '
{{ foo.bar }}
'
})
- class Test {
+ export class Test {
foo = {};
}`;
assertSuccess('no-access-missing-member', source);
@@ -507,21 +537,22 @@ describe('no-access-missing-member', () => {
it('should work with existing single-level non-existing nested properties', () => {
let source = `
+
@Component({
selector: 'foobar',
- template: '
{{ foo.bar }}
+ template: '
{{ foo.bar }}
'
})
- class Test {
+ export class Test {
foo1 = {};
}`;
assertFailure('no-access-missing-member', source, {
message: 'The property "foo" that you\'re trying to access does not exist in the class declaration. Probably you mean: "foo1".',
startPosition: {
- line: 3,
+ line: 4,
character: 29
},
endPosition: {
- line: 3,
+ line: 4,
character: 32
}
});
@@ -529,11 +560,12 @@ describe('no-access-missing-member', () => {
it('should work with existing properties and pipes', () => {
let source = `
+
@Component({
selector: 'foobar',
- template: '
{{ foo | baz }}
+ template: '
{{ foo | baz }}
'
})
- class Test {
+ export class Test {
foo = {};
}`;
assertSuccess('no-access-missing-member', source);
@@ -541,11 +573,12 @@ describe('no-access-missing-member', () => {
it('should work with existing properties and pipes', () => {
let source = `
+
@Component({
selector: 'foobar',
- template: '
{{ foo.baz() }}
+ template: '
{{ foo.baz() }}
'
})
- class Test {
+ export class Test {
foo = {};
}`;
assertSuccess('no-access-missing-member', source);
@@ -553,11 +586,12 @@ describe('no-access-missing-member', () => {
it('should work with existing properties and pipes', () => {
let source = `
+
@Component({
selector: 'foobar',
template: \`\`
})
- class Test {
+ export class Test {
showMenu = {};
}`;
assertSuccess('no-access-missing-member', source);
@@ -565,6 +599,7 @@ describe('no-access-missing-member', () => {
it('should work with inputs with string values', () => {
let source = `
+
@Component({
selector: 'foobar',
template: \`
@@ -574,7 +609,7 @@ describe('no-access-missing-member', () => {
\`
})
- class Test {
+ export class Test {
public hasOrdered: boolean;
}`;
assertSuccess('no-access-missing-member', source);
@@ -582,11 +617,12 @@ describe('no-access-missing-member', () => {
it('should work with getters', () => {
let source = `
+
@Component({
selector: 'foobar',
template: \`{{ bar }}\`
})
- class Test {
+ export class Test {
get bar() {
return 42;
}
@@ -596,11 +632,12 @@ describe('no-access-missing-member', () => {
it('should work with setters', () => {
let source = `
+
@Component({
selector: 'foobar',
template: \`\`
})
- class Test {
+ export class Test {
set bar() {
}
}`;
@@ -623,6 +660,7 @@ describe('no-access-missing-member', () => {
it('should work with getters', () => {
let source = `
+
@Component({
template: \`