diff --git a/src/index.ts b/src/index.ts
index d20b4449e..e5782e32d 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -29,6 +29,7 @@ export { Rule as PreferOutputReadonlyRule } from './preferOutputReadonlyRule';
export { Rule as TemplateConditionalComplexityRule } from './templateConditionalComplexityRule';
export { Rule as TemplateCyclomaticComplexityRule } from './templateCyclomaticComplexityRule';
export { Rule as TemplateAccessibilityTabindexNoPositiveRule } from './templateAccessibilityTabindexNoPositiveRule';
+export { Rule as TemplateNoAnyRule } from './templateNoAnyRule';
export { Rule as TemplateAccessibilityLabelForVisitor } from './templateAccessibilityLabelForRule';
export { Rule as TemplateAccessibilityValidAriaRule } from './templateAccessibilityValidAriaRule';
export { Rule as TemplatesAccessibilityAnchorContentRule } from './templateAccessibilityAnchorContentRule';
diff --git a/src/templateNoAnyRule.ts b/src/templateNoAnyRule.ts
new file mode 100644
index 000000000..1a0448a7e
--- /dev/null
+++ b/src/templateNoAnyRule.ts
@@ -0,0 +1,58 @@
+import { MethodCall, PropertyRead } from '@angular/compiler';
+import { IRuleMetadata, RuleFailure } from 'tslint';
+import { AbstractRule } from 'tslint/lib/rules';
+import { dedent } from 'tslint/lib/utils';
+import { SourceFile } from 'typescript';
+import { NgWalker } from './angular/ngWalker';
+import { RecursiveAngularExpressionVisitor } from './angular/templates/recursiveAngularExpressionVisitor';
+
+const ANY_TYPE_CAST_FUNCTION_NAME = '$any';
+
+export class Rule extends AbstractRule {
+ static readonly metadata: IRuleMetadata = {
+ description: `Disallows using '${ANY_TYPE_CAST_FUNCTION_NAME}' in templates.`,
+ options: null,
+ optionsDescription: 'Not configurable.',
+ rationale: dedent`
+ The use of '${ANY_TYPE_CAST_FUNCTION_NAME}' nullifies the compile-time
+ benefits of the Angular's type system.
+ `,
+ ruleName: 'template-no-any',
+ type: 'functionality',
+ typescriptOnly: true
+ };
+
+ static readonly FAILURE_STRING = `Avoid using '${ANY_TYPE_CAST_FUNCTION_NAME}' in templates`;
+
+ apply(sourceFile: SourceFile): RuleFailure[] {
+ return this.applyWithWalker(
+ new NgWalker(sourceFile, this.getOptions(), {
+ expressionVisitorCtrl: ExpressionVisitor
+ })
+ );
+ }
+}
+
+class ExpressionVisitor extends RecursiveAngularExpressionVisitor {
+ visitMethodCall(ast: MethodCall, context: any): any {
+ this.validateMethodCall(ast);
+ super.visitMethodCall(ast, context);
+ }
+
+ private generateFailure(ast: MethodCall): void {
+ const {
+ span: { end: endSpan, start: startSpan }
+ } = ast;
+
+ this.addFailureFromStartToEnd(startSpan, endSpan, Rule.FAILURE_STRING);
+ }
+
+ private validateMethodCall(ast: MethodCall): void {
+ const isAnyTypeCastFunction = ast.name === ANY_TYPE_CAST_FUNCTION_NAME;
+ const isAngularAnyTypeCastFunction = !(ast.receiver instanceof PropertyRead);
+
+ if (!isAnyTypeCastFunction || !isAngularAnyTypeCastFunction) return;
+
+ this.generateFailure(ast);
+ }
+}
diff --git a/test/templateNoAnyRule.spec.ts b/test/templateNoAnyRule.spec.ts
new file mode 100644
index 000000000..758377422
--- /dev/null
+++ b/test/templateNoAnyRule.spec.ts
@@ -0,0 +1,202 @@
+import { Rule } from '../src/templateNoAnyRule';
+import { assertAnnotated, assertMultipleAnnotated, assertSuccess } from './testHelper';
+
+const {
+ FAILURE_STRING,
+ metadata: { ruleName }
+} = Rule;
+
+describe(ruleName, () => {
+ describe('failure', () => {
+ it('should fail with call expression in expression binding', () => {
+ const source = `
+ @Component({
+ template: '{{ $any(framework).name }}'
+ ~~~~~~~~~~~~~~~
+ })
+ export class Bar {}
+ `;
+ assertAnnotated({
+ message: FAILURE_STRING,
+ ruleName,
+ source
+ });
+ });
+
+ it('should fail with call expression using "this"', () => {
+ const source = `
+ @Component({
+ template: '{{ this.$any(framework).name }}'
+ ~~~~~~~~~~~~~~~~~~~~
+ })
+ class Bar {}
+ `;
+ assertAnnotated({
+ message: FAILURE_STRING,
+ ruleName,
+ source
+ });
+ });
+
+ it('should fail with call expression in property binding', () => {
+ const source = `
+ @Component({
+ template: 'Click here'
+ ~~~~~~~~~~~~~~~
+ })
+ class Bar {}
+ `;
+ assertAnnotated({
+ message: FAILURE_STRING,
+ ruleName,
+ source
+ });
+ });
+
+ it('should fail with call expression in an output handler', () => {
+ const source = `
+ @Component({
+ template: ''
+ ~~~~~~~~~~
+ })
+ class Bar {}
+ `;
+ assertAnnotated({
+ message: FAILURE_STRING,
+ ruleName,
+ source
+ });
+ });
+
+ it('should fail for multiple cases', () => {
+ const source = `
+ @Component({
+ template: \`
+ {{ $any(framework).name }}
+ ~~~~~~~~~~~~~~~
+ {{ this.$any(framework).name }}
+ ^^^^^^^^^^^^^^^^^^^^
+ Click here'
+ ###############
+
+ %%%%%%%%%%
+ \`
+ })
+ class Bar {}
+ `;
+ assertMultipleAnnotated({
+ failures: [
+ {
+ char: '~',
+ msg: FAILURE_STRING
+ },
+ {
+ char: '^',
+ msg: FAILURE_STRING
+ },
+ {
+ char: '#',
+ msg: FAILURE_STRING
+ },
+ {
+ char: '%',
+ msg: FAILURE_STRING
+ }
+ ],
+ ruleName,
+ source
+ });
+ });
+ });
+
+ describe('success', () => {
+ it('should pass with no call expression', () => {
+ const source = `
+ @Component({
+ template: '{{ $any }}'
+ })
+ class Bar {}
+ `;
+ assertSuccess(ruleName, source);
+ });
+
+ it('should pass for an object containing a function called "$any"', () => {
+ const source = `
+ @Component({
+ template: '{{ obj.$any() }}'
+ })
+ class Bar {
+ readonly obj = {
+ $any: () => '$any'
+ };
+ }
+ `;
+ assertSuccess(ruleName, source);
+ });
+
+ it('should pass for a nested object containing a function called "$any"', () => {
+ const source = `
+ @Component({
+ template: '{{ obj?.x?.y!.z!.$any() }}'
+ })
+ class Bar {
+ readonly obj: Partial = {
+ x: {
+ y: {
+ z: {
+ $any: () => '$any'
+ }
+ }
+ }
+ };
+ }
+ `;
+ assertSuccess(ruleName, source);
+ });
+
+ it('should pass with call expression in property binding', () => {
+ const source = `
+ @Component({
+ template: 'Click here'
+ })
+ class Bar {}
+ `;
+ assertSuccess(ruleName, source);
+ });
+
+ it('should pass with call expression in an output handler', () => {
+ const source = `
+ @Component({
+ template: ''
+ })
+ class Bar {}
+ `;
+ assertSuccess(ruleName, source);
+ });
+
+ it('should pass for multiple cases', () => {
+ const source = `
+ @Component({
+ template: \`
+ {{ $any }}
+ {{ obj?.x?.y!.z!.$any() }}
+ Click here
+
+ \`
+ })
+ class Bar {
+ readonly obj: Partial = {
+ x: {
+ y: {
+ z: {
+ $any: () => '$any'
+ }
+ }
+ }
+ };
+ }
+ `;
+ assertSuccess(ruleName, source);
+ });
+ });
+});