From 9fbfdb3f55aa876a560fc0f47a6c7e36af5507b8 Mon Sep 17 00:00:00 2001 From: Gregor Woiwode Date: Wed, 3 May 2017 00:13:02 +0200 Subject: [PATCH 1/9] feat(use-view-encapsulation): declare specs for rule --- test/viewEncapsulationrule.spec.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 test/viewEncapsulationrule.spec.ts diff --git a/test/viewEncapsulationrule.spec.ts b/test/viewEncapsulationrule.spec.ts new file mode 100644 index 000000000..a4b99113a --- /dev/null +++ b/test/viewEncapsulationrule.spec.ts @@ -0,0 +1,10 @@ +describe('use-view-encapsulation', () => { + describe('invalid view encapsulation', () => { + it('should fail if ViewEncapsulation.None is set'); + }); + + describe('valid view encapsulation', () => { + it('should succeed if ViewEncapsulation.Native is set'); + it('should succeed if ViewEncapsulation.Emulated is set'); + }); +}); From 939967c240893f1eb079cde40fbaec246bd49312 Mon Sep 17 00:00:00 2001 From: Gregor Woiwode Date: Wed, 3 May 2017 00:25:04 +0200 Subject: [PATCH 2/9] feat(use-view-encapsulation): add test sources beeing linted --- test/viewEncapsulationrule.spec.ts | 32 +++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/test/viewEncapsulationrule.spec.ts b/test/viewEncapsulationrule.spec.ts index a4b99113a..9ac7b3a5c 100644 --- a/test/viewEncapsulationrule.spec.ts +++ b/test/viewEncapsulationrule.spec.ts @@ -1,10 +1,36 @@ describe('use-view-encapsulation', () => { describe('invalid view encapsulation', () => { - it('should fail if ViewEncapsulation.None is set'); + it('should fail if ViewEncapsulation.None is set', () => { + const source = ` + @Component({ + selector: 'sg-foo-bar', + encapsulation: ViewEncapsulation.None + ~~~~ + }) + export class TestComponent { } + `; + }); }); describe('valid view encapsulation', () => { - it('should succeed if ViewEncapsulation.Native is set'); - it('should succeed if ViewEncapsulation.Emulated is set'); + it('should succeed if ViewEncapsulation.Native is set', () => { + const source = ` + @Component({ + selector: 'sg-foo-bar', + encapsulation: ViewEncapsulation.Native + }) + export class TestComponent { } + `; + }); + + it('should succeed if ViewEncapsulation.Emulated is set', () => { + const source = ` + @Component({ + selector: 'sg-foo-bar', + encapsulation: ViewEncapsulation.Emulated + }) + export class TestComponent { } + `; + }); }); }); From c7bc7887fb75a9eb99b1b1ee376b711100a4e279 Mon Sep 17 00:00:00 2001 From: Gregor Woiwode Date: Wed, 3 May 2017 00:35:25 +0200 Subject: [PATCH 3/9] feat(use-view-encapsulation): add assertions --- test/viewEncapsulationrule.spec.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/viewEncapsulationrule.spec.ts b/test/viewEncapsulationrule.spec.ts index 9ac7b3a5c..68bab699c 100644 --- a/test/viewEncapsulationrule.spec.ts +++ b/test/viewEncapsulationrule.spec.ts @@ -1,3 +1,5 @@ +import { assertAnnotated, assertSuccess } from './testHelper'; + describe('use-view-encapsulation', () => { describe('invalid view encapsulation', () => { it('should fail if ViewEncapsulation.None is set', () => { @@ -9,6 +11,12 @@ describe('use-view-encapsulation', () => { }) export class TestComponent { } `; + + assertAnnotated({ + ruleName: 'use-view-encapsulation', + message: 'Using "ViewEncapsulation.None" may cause conflicts between css rules having the same selector', + source + }); }); }); @@ -21,6 +29,8 @@ describe('use-view-encapsulation', () => { }) export class TestComponent { } `; + + assertSuccess('use-view-encapsulation', source); }); it('should succeed if ViewEncapsulation.Emulated is set', () => { @@ -31,6 +41,8 @@ describe('use-view-encapsulation', () => { }) export class TestComponent { } `; + + assertSuccess('use-view-encapsulation', source); }); }); }); From 7e1962e89c991eb759418210e23df40ee22fa5df Mon Sep 17 00:00:00 2001 From: Gregor Woiwode Date: Wed, 3 May 2017 01:07:45 +0200 Subject: [PATCH 4/9] feat(use-view-encapsulation): rename viewEncapsulationRule to useViewEncapsulationRule --- ...Encapsulationrule.spec.ts => useViewEncapsulationRule.spec.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{viewEncapsulationrule.spec.ts => useViewEncapsulationRule.spec.ts} (100%) diff --git a/test/viewEncapsulationrule.spec.ts b/test/useViewEncapsulationRule.spec.ts similarity index 100% rename from test/viewEncapsulationrule.spec.ts rename to test/useViewEncapsulationRule.spec.ts From 70b62a9f0652d3e48d5493bef948f8b96d4a2625 Mon Sep 17 00:00:00 2001 From: Gregor Woiwode Date: Wed, 3 May 2017 10:00:12 +0200 Subject: [PATCH 5/9] feat(use-view-encapsulation): add rule checking view encapsulation --- src/index.ts | 1 + src/useViewEncapsulationRule.ts | 44 +++++++++++++++++++++++++++ test/useViewEncapsulationRule.spec.ts | 2 +- 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 src/useViewEncapsulationRule.ts diff --git a/src/index.ts b/src/index.ts index 307db019a..12c2ccf67 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,5 +20,6 @@ export { Rule as UseOutputPropertyDecoratorRule } from './useOutputPropertyDecor export { Rule as UsePipeTransformInterfaceRule } from './usePipeTransformInterfaceRule'; export { Rule as TemplateToNgTemplateRule } from './templateToNgTemplateRule'; export { Rule as UsePipeDecoratorRule } from './usePipeDecoratorRule'; +export { Rule as UseViewEncapsulationRule } from './useViewEncapsulationRule'; export * from './angular/config'; diff --git a/src/useViewEncapsulationRule.ts b/src/useViewEncapsulationRule.ts new file mode 100644 index 000000000..1cb472015 --- /dev/null +++ b/src/useViewEncapsulationRule.ts @@ -0,0 +1,44 @@ +import { getComponentDecorator, getDecoratorPropertyInitializer } from './util/utils'; +import { ComponentMetadata } from './angular/metadata'; +import * as Lint from 'tslint'; +import * as ts from 'typescript'; + +import { NgWalker } from './angular/ngWalker'; + +export class Rule extends Lint.Rules.AbstractRule { + + static metadata: Lint.IRuleMetadata = { + ruleName: 'use-view-encapsulation', + type: 'maintainability', + description: 'Disallows using of ViewEncapsulation.None', + rationale: '', + options: null, + optionsDescription: 'Not configurable', + typescriptOnly: true + }; + + static FAILURE = 'Using "ViewEncapsulation.None" may cause conflicts between css rules having the same selector'; + + apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + const walker = new ViewEncapsulationWalker(sourceFile, this.getOptions()); + return this.applyWithWalker(walker); + } +} + +class ViewEncapsulationWalker extends NgWalker { + + visitClassDeclaration(node: ts.ClassDeclaration) { + const d = getComponentDecorator(node); + const encapsulation = getDecoratorPropertyInitializer(d, 'encapsulation'); + + if(encapsulation.name.text !== 'None') { return; } + + this.addFailure( + this.createFailure( + encapsulation.getStart(), + encapsulation.getWidth(), + Rule.FAILURE + ) + ); + } +} diff --git a/test/useViewEncapsulationRule.spec.ts b/test/useViewEncapsulationRule.spec.ts index 68bab699c..962861ef5 100644 --- a/test/useViewEncapsulationRule.spec.ts +++ b/test/useViewEncapsulationRule.spec.ts @@ -7,7 +7,7 @@ describe('use-view-encapsulation', () => { @Component({ selector: 'sg-foo-bar', encapsulation: ViewEncapsulation.None - ~~~~ + ~~~~~~~~~~~~~~~~~~~~~~ }) export class TestComponent { } `; From d3b078cdf4111e7fca766d6d96f52040c93e2309 Mon Sep 17 00:00:00 2001 From: Gregor Woiwode Date: Wed, 3 May 2017 21:15:43 +0200 Subject: [PATCH 6/9] feat(use-view-encapsulation): avoid crash when ViewEncapsulation is not set explicitly --- src/useViewEncapsulationRule.ts | 9 ++++++--- test/useViewEncapsulationRule.spec.ts | 11 +++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/useViewEncapsulationRule.ts b/src/useViewEncapsulationRule.ts index 1cb472015..74494016e 100644 --- a/src/useViewEncapsulationRule.ts +++ b/src/useViewEncapsulationRule.ts @@ -28,10 +28,13 @@ export class Rule extends Lint.Rules.AbstractRule { class ViewEncapsulationWalker extends NgWalker { visitClassDeclaration(node: ts.ClassDeclaration) { - const d = getComponentDecorator(node); - const encapsulation = getDecoratorPropertyInitializer(d, 'encapsulation'); + const decorator = getComponentDecorator(node); + const encapsulation = getDecoratorPropertyInitializer(decorator, 'encapsulation'); - if(encapsulation.name.text !== 'None') { return; } + if(!encapsulation || + encapsulation.name.text !== 'None') { + return; + } this.addFailure( this.createFailure( diff --git a/test/useViewEncapsulationRule.spec.ts b/test/useViewEncapsulationRule.spec.ts index 962861ef5..c43ef9964 100644 --- a/test/useViewEncapsulationRule.spec.ts +++ b/test/useViewEncapsulationRule.spec.ts @@ -44,5 +44,16 @@ describe('use-view-encapsulation', () => { assertSuccess('use-view-encapsulation', source); }); + + it('should succeed if no ViewEncapsulation is set explicitly', () => { + const source = ` + @Component({ + selector: 'sg-foo-bar', + }) + export class TestComponent { } + `; + + assertSuccess('use-view-encapsulation', source); + }); }); }); From e252a6abf809313a3dc0fea5cf0a78a51a4fdfa9 Mon Sep 17 00:00:00 2001 From: Gregor Woiwode Date: Wed, 3 May 2017 21:28:22 +0200 Subject: [PATCH 7/9] (fix): remove trailing whitespaces --- test/directiveClassSuffix.spec.ts | 2 +- test/importDestructuringSpacingRule.spec.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/directiveClassSuffix.spec.ts b/test/directiveClassSuffix.spec.ts index 6c56bac58..b0e037e19 100644 --- a/test/directiveClassSuffix.spec.ts +++ b/test/directiveClassSuffix.spec.ts @@ -8,7 +8,7 @@ describe('directive-class-suffix', () => { selector: 'sgBarFoo' }) class Test {} - ~~~~ + ~~~~ `; assertAnnotated({ ruleName: 'directive-class-suffix', diff --git a/test/importDestructuringSpacingRule.spec.ts b/test/importDestructuringSpacingRule.spec.ts index 70a43b763..4611b74cc 100644 --- a/test/importDestructuringSpacingRule.spec.ts +++ b/test/importDestructuringSpacingRule.spec.ts @@ -5,7 +5,7 @@ describe('import-destructuring-spacing', () => { it('should fail when the imports have no spaces', () => { let source = ` import {Foo} from './foo' - ~~~~~ + ~~~~~ `; assertAnnotated({ ruleName: 'import-destructuring-spacing', @@ -29,7 +29,7 @@ describe('import-destructuring-spacing', () => { it('should fail with spaces between items', () => { let source = ` import {Foo, Bar} from './foo' - ~~~~~~~~~~~ + ~~~~~~~~~~~ `; assertAnnotated({ ruleName: 'import-destructuring-spacing', From ee6afbdff494d929f2983d2e52692856bbe04705 Mon Sep 17 00:00:00 2001 From: Gregor Woiwode Date: Wed, 3 May 2017 22:12:39 +0200 Subject: [PATCH 8/9] feat(use-view-encapsulation): document rule use-view-encapsulation --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ac2fc06cd..6f8c23138 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,8 @@ Create the following `tslint.json` file like: "use-input-property-decorator": true, "use-output-property-decorator": true, "use-host-property-decorator": true, + "use-host-property-decorator": true, + "use-view-encapsulation": true, "no-attribute-parameter-decorator": true, "no-input-rename": true, "no-output-rename": true, From 88e3cd417204b063e1bd2155c29de3bd7a2132fa Mon Sep 17 00:00:00 2001 From: Gregor Woiwode Date: Thu, 4 May 2017 00:02:30 +0200 Subject: [PATCH 9/9] feat(use-view-encapsulation): simplify warning message --- .vscode/settings.json | 4 +++- src/useViewEncapsulationRule.ts | 2 +- test/useViewEncapsulationRule.spec.ts | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index c7c1623bc..0f3d9ea7b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,5 @@ { - "typescript.tsdk": "./node_modules/typescript/lib" + "typescript.tsdk": "./node_modules/typescript/lib", + "angulardoc.repoId": "51f64839-b313-47fa-8d89-9317081ebe22", + "angulardoc.lastSync": 0 } \ No newline at end of file diff --git a/src/useViewEncapsulationRule.ts b/src/useViewEncapsulationRule.ts index 74494016e..1aecfed1c 100644 --- a/src/useViewEncapsulationRule.ts +++ b/src/useViewEncapsulationRule.ts @@ -17,7 +17,7 @@ export class Rule extends Lint.Rules.AbstractRule { typescriptOnly: true }; - static FAILURE = 'Using "ViewEncapsulation.None" may cause conflicts between css rules having the same selector'; + static FAILURE = 'Using "ViewEncapsulation.None" will make your styles global which may have unintended effect'; apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { const walker = new ViewEncapsulationWalker(sourceFile, this.getOptions()); diff --git a/test/useViewEncapsulationRule.spec.ts b/test/useViewEncapsulationRule.spec.ts index c43ef9964..d0bb86611 100644 --- a/test/useViewEncapsulationRule.spec.ts +++ b/test/useViewEncapsulationRule.spec.ts @@ -14,7 +14,7 @@ describe('use-view-encapsulation', () => { assertAnnotated({ ruleName: 'use-view-encapsulation', - message: 'Using "ViewEncapsulation.None" may cause conflicts between css rules having the same selector', + message: 'Using "ViewEncapsulation.None" will make your styles global which may have unintended effect', source }); });