From aba6408d3d28dbe6b203963a2fb69cfb67e44ec2 Mon Sep 17 00:00:00 2001 From: d0whc3r Date: Sun, 15 Sep 2019 17:30:36 +0200 Subject: [PATCH] feat(own-methods-must-be-private): add new rule --- README.md | 5 ++ docs/own-methods-must-be-private.md | 15 ++++++ src/configs/recommended.ts | 1 + src/rules/index.ts | 2 + src/rules/own-methods-must-be-private.ts | 48 +++++++++++++++++++ src/utils.ts | 12 +++++ .../own-methods-must-be-private.good.tsx | 18 +++++++ .../own-methods-must-be-private.spec.ts | 27 +++++++++++ .../own-methods-must-be-private.wrong.tsx | 27 +++++++++++ 9 files changed, 155 insertions(+) create mode 100644 docs/own-methods-must-be-private.md create mode 100644 src/rules/own-methods-must-be-private.ts create mode 100644 tests/lib/rules/own-methods-must-be-private/own-methods-must-be-private.good.tsx create mode 100644 tests/lib/rules/own-methods-must-be-private/own-methods-must-be-private.spec.ts create mode 100644 tests/lib/rules/own-methods-must-be-private/own-methods-must-be-private.wrong.tsx diff --git a/README.md b/README.md index d6afd92..114ee18 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,10 @@ This rule catches Stencil Methods marked as private or protected. This rule catches Stencil Watchs with non existing Props or States. +- [`@d0whc3r/stencil/own-methods-must-be-private`](./docs/own-methods-must-be-private.md) + +This rule catches own class methods marked as public. + - [`@d0whc3r/stencil/own-props-must-be-private`](./docs/own-props-must-be-private.md) This rule catches own class properties marked as public. @@ -120,6 +124,7 @@ This rule catches modules that expose more than just the Stencil Component itsel "@d0whc3r/stencil/host-data-deprecated": "error", "@d0whc3r/stencil/methods-must-be-public": "error", "@d0whc3r/stencil/no-unused-watch": "error", + "@d0whc3r/stencil/own-methods-must-be-private": "error", "@d0whc3r/stencil/own-props-must-be-private": "error", "@d0whc3r/stencil/prefer-vdom-listener": "error", "@d0whc3r/stencil/props-must-be-public": "error", diff --git a/docs/own-methods-must-be-private.md b/docs/own-methods-must-be-private.md new file mode 100644 index 0000000..fcf77ef --- /dev/null +++ b/docs/own-methods-must-be-private.md @@ -0,0 +1,15 @@ +# own-methods-must-be-private + +Ensures that all own class properties are private. + +## Config + +No config is needed + +## Usage + +```json +{ "@d0whc3r/stencil/own-methods-must-be-private": "error" } +``` + +> Fix included diff --git a/src/configs/recommended.ts b/src/configs/recommended.ts index 300e9cc..04f622a 100644 --- a/src/configs/recommended.ts +++ b/src/configs/recommended.ts @@ -28,6 +28,7 @@ export default { '@d0whc3r/stencil/host-data-deprecated': 'error', '@d0whc3r/stencil/methods-must-be-public': 'error', '@d0whc3r/stencil/no-unused-watch': 'error', + '@d0whc3r/stencil/own-methods-must-be-private': 'error', '@d0whc3r/stencil/own-props-must-be-private': 'error', '@d0whc3r/stencil/prefer-vdom-listener': 'error', '@d0whc3r/stencil/props-must-be-public': 'error', diff --git a/src/rules/index.ts b/src/rules/index.ts index 2a00938..757aaf0 100644 --- a/src/rules/index.ts +++ b/src/rules/index.ts @@ -7,6 +7,7 @@ import elementType from './element-type'; import hostDataDeprecated from './host-data-deprecated'; import methodsMustBePublic from './methods-must-be-public'; import noUnusedWatch from './no-unused-watch'; +import ownMethodsMustBePrivate from './own-methods-must-be-private'; import ownPropsMustBePrivate from './own-props-must-be-private'; import preferVdomListener from './prefer-vdom-listener'; import propsMustBePublic from './props-must-be-public'; @@ -27,6 +28,7 @@ export default { 'host-data-deprecated': hostDataDeprecated, 'methods-must-be-public': methodsMustBePublic, 'no-unused-watch': noUnusedWatch, + 'own-methods-must-be-private': ownMethodsMustBePrivate, 'own-props-must-be-private': ownPropsMustBePrivate, 'prefer-vdom-listener': preferVdomListener, 'props-must-be-public': propsMustBePublic, diff --git a/src/rules/own-methods-must-be-private.ts b/src/rules/own-methods-must-be-private.ts new file mode 100644 index 0000000..5a8d1f3 --- /dev/null +++ b/src/rules/own-methods-must-be-private.ts @@ -0,0 +1,48 @@ +import { Rule } from 'eslint'; +import ts from 'typescript'; +import { isPrivate, stencilComponentContext, stencilDecorators, stencilLifecycle } from '../utils'; + +const rule: Rule.RuleModule = { + meta: { + docs: { + description: 'This rule catches own class methods marked as public.', + category: 'Possible Errors', + recommended: true + }, + schema: [], + type: 'problem', + fixable: 'code' + }, + + create(context): Rule.RuleListener { + const stencil = stencilComponentContext(); + + const parserServices = context.parserServices; + return { + ...stencil.rules, + 'MethodDefinition': (node: any) => { + if (!stencil.isComponent()) { + return; + } + const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node); + const stencilDecorator = originalNode.decorators && originalNode.decorators.some( + (dec: any) => stencilDecorators.includes(dec.expression.expression.escapedText)); + const stencilCycle = stencilLifecycle.includes(originalNode.name.escapedText); + if (!stencilDecorator && !stencilCycle && !isPrivate(originalNode)) { + const text = String(originalNode.getFullText()); + context.report({ + node: node, + message: `Own class methods cannot be public`, + fix(fixer) { + const methodName = node.key.name; + const result = text.replace('public ', '').replace(methodName, `private ${methodName}`); + return fixer.replaceText(node, result); + } + }); + } + } + }; + } +}; + +export default rule; diff --git a/src/utils.ts b/src/utils.ts index b1b8099..89ba0f8 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -59,3 +59,15 @@ export function getType(node: any) { } export const stencilDecorators = ['Component', 'Prop', 'State', 'Watch', 'Element', 'Method', 'Event', 'Listen']; + +export const stencilLifecycle = [ + 'connectedCallback', + 'disconnectedCallback', + 'componentWillLoad', + 'componentDidLoad', + 'componentWillRender', + 'componentDidRender', + 'componentWillUpdate', + 'componentDidUpdate', + 'render' +]; diff --git a/tests/lib/rules/own-methods-must-be-private/own-methods-must-be-private.good.tsx b/tests/lib/rules/own-methods-must-be-private/own-methods-must-be-private.good.tsx new file mode 100644 index 0000000..523463e --- /dev/null +++ b/tests/lib/rules/own-methods-must-be-private/own-methods-must-be-private.good.tsx @@ -0,0 +1,18 @@ +@Component({ tag: 'sample-tag' }) +export class SampleTag { + @Prop() readonly test?: string; + @Prop({ mutable: true }) testMutable?: string; + + private internalMethod() { + return 'ok'; + } + + @OwnDecorator() + private internalDecoratedMethod() { + return 'ok'; + }; + + render() { + return (
test
); + } +} diff --git a/tests/lib/rules/own-methods-must-be-private/own-methods-must-be-private.spec.ts b/tests/lib/rules/own-methods-must-be-private/own-methods-must-be-private.spec.ts new file mode 100644 index 0000000..503ea55 --- /dev/null +++ b/tests/lib/rules/own-methods-must-be-private/own-methods-must-be-private.spec.ts @@ -0,0 +1,27 @@ +import rule from '../../../../src/rules/own-methods-must-be-private'; +import { ruleTester } from '../rule-tester'; +import * as path from 'path'; +import * as fs from 'fs'; + +describe('stencil rules', () => { + const files = { + good: path.resolve(__dirname, 'own-methods-must-be-private.good.tsx'), + wrong: path.resolve(__dirname, 'own-methods-must-be-private.wrong.tsx') + }; + ruleTester.run('own-methods-must-be-private', rule, { + valid: [ + { + code: fs.readFileSync(files.good, 'utf8'), + filename: files.good + } + ], + + invalid: [ + { + code: fs.readFileSync(files.wrong, 'utf8'), + filename: files.wrong, + errors: 4 + } + ] + }); +}); diff --git a/tests/lib/rules/own-methods-must-be-private/own-methods-must-be-private.wrong.tsx b/tests/lib/rules/own-methods-must-be-private/own-methods-must-be-private.wrong.tsx new file mode 100644 index 0000000..a4a3837 --- /dev/null +++ b/tests/lib/rules/own-methods-must-be-private/own-methods-must-be-private.wrong.tsx @@ -0,0 +1,27 @@ +@Component({ tag: 'sample-tag' }) +export class SampleTag { + @Prop() readonly test?: string; + @Prop({ mutable: true }) testMutable?: string; + + internalMethod() { + return 'ok'; + } + + public internalMethod2() { + return 'ok'; + } + + @OwnDecorator() + internalDecoratedMethod() { + return 'ok'; + }; + + @OwnDecorator() + public internalDecoratedMethod2() { + return 'ok'; + }; + + render() { + return (
test
); + } +}