Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

ESLint: Low Hanging Fruits

ESLint: Low Hanging Fruits

A codebase without @Input() but only input()? No *ngif but @if? How to enforce that? We know that ESLint is the ideal tool for maintaining code quality, but standard linting rules have their limits. Creating custom rules might seem daunting, especially when terms like Abstract Syntax Tree (AST) and flat config show up. Don’t be intimidated. ESLint customization is actually very straightforward. With a few key concepts, you can easily extend its functionality, and this is exactly what my talk will cover.

Rainer Hahnekamp

August 21, 2024
Tweet

More Decks by Rainer Hahnekamp

Other Decks in Programming

Transcript

  1. RainerHahnekamp About Me... https://www.youtube.com/ @RainerHahnekamp https://www.ng-news.com https://github.com/softarc-consulting/sheriff • Rainer Hahnekamp

    ANGULARarchitects.io NgRx Team (Trusted Collaborator) • Developer / Trainer / Speaker @RainerHahnekamp Workshops NgRx • Testing • Spring • Quality
  2. RainerHahnekamp Legacy and Flat Config • How to know? ◦

    Flat: eslint.config.[m]js ◦ Legacy: .eslintrc.json legacy • Flat is default since angular-eslint 18
  3. RainerHahnekamp MVP: Define Plugin export default { plugins: { custom:

    { rules: { 'capitalized-classes': { create(context) { return { ClassDeclaration(node) { context.report({node, message: "This is a class"}) } }; } } } } } }
  4. RainerHahnekamp MVP: Activate Plugin export default { plugins: { custom:

    { rules: { 'capitalized-classes': { create(context) { return { ClassDeclaration(node) { if (node.id.name.match(/^[a-z]/)) { context.report({node, message: "This is a class"}) } } // ...
  5. RainerHahnekamp MVP: Do some checks export default { plugins: {

    custom: { rules: { 'capitalized-classes': { meta: { messages: { capitalizeClass: 'class names have to be capitalized' } }, create(context) { return { ClassDeclaration(node) { if (node.id.name.match(/^[a-z]/)) { context.report({ node, messageId: 'capitalizeClass' }) } // ...
  6. RainerHahnekamp TypeScript Support I: Type Checking // @ts-check import tseslint

    from 'typescript-eslint'; export default tseslint.config()
  7. RainerHahnekamp TypeScript Support I: Recommended ESLint rules // @ts-check import

    tseslint from 'typescript-eslint'; export default tseslint.config( eslint.configs.recommended )
  8. RainerHahnekamp TypeScript Support I: Recommended TSESLint rules // @ts-check import

    tseslint from 'typescript-eslint'; import eslint from '@eslint/js' export default tseslint.config( eslint.configs.recommended, ...tseslint.configs.recommended, )
  9. RainerHahnekamp TypeScript Support I: Adding Custom Plugin // @ts-check import

    tseslint from 'typescript-eslint'; import eslint from '@eslint/js' export default tseslint.config( eslint.configs.recommended, ...tseslint.configs.recommended, { plugins: { // ... }, rules: { 'custom/capitalized-classes': 'error' } })
  10. RainerHahnekamp TypeScript Support I: Disabling other Rules // @ts-check import

    tseslint from 'typescript-eslint'; import eslint from '@eslint/js' export default tseslint.config( eslint.configs.recommended, ...tseslint.configs.recommended, { plugins: { // ... }, rules: { 'custom/capitalized-classes': 'error', '@typescript-eslint/no-unused-vars': 'off' } })
  11. RainerHahnekamp TypeScript II: Typed Plugin import {ESLintUtils} from "@typescript-eslint/utils"; const

    createRule = ESLintUtils.RuleCreator(name => ''); const capitalizeClasses = createRule({ name: 'capitalize-class' })
  12. RainerHahnekamp TypeScript II: Typed Plugin import {ESLintUtils} from "@typescript-eslint/utils"; const

    createRule = ESLintUtils.RuleCreator(name => ''); const capitalizeClasses = createRule({ name: 'capitalize-class', defaultOptions: [], meta: { type: 'problem', docs: {description: 'Capitalize classes'}, messages: { capitalizeClass: 'Class names have to be capitalized' }, schema: [] } })
  13. RainerHahnekamp TypeScript II: Typed Plugin import {ESLintUtils} from "@typescript-eslint/utils"; const

    createRule = ESLintUtils.RuleCreator(name => ''); const capitalizeClasses = createRule({ name: 'capitalize-class', defaultOptions: [], meta: { type: 'problem', docs: {description: 'Capitalize classes'}, messages: { capitalizeClass: 'Class names have to be capitalized' }, schema: [] }, create(context) {...} })
  14. RainerHahnekamp TypeScript II: Typed Plugin import {ESLintUtils} from "@typescript-eslint/utils"; const

    createRule = ESLintUtils.RuleCreator(name => ''); const capitalizeClasses = createRule({ name: 'capitalize-class', defaultOptions: [], meta: { type: 'problem', docs: {description: 'Capitalize classes'}, messages: { capitalizeClass: 'Class names have to be capitalized'
  15. RainerHahnekamp TypeScript II: Typed Plugin const plugin = { plugins:

    { custom: { rules: { 'capitalized-classes': capitalizeClasses } } } } export = plugin
  16. RainerHahnekamp TypeScript II: Typed Plugin // @ts-check import tseslint from

    'typescript-eslint'; import eslint from '@eslint/js' export default tseslint.config( eslint.configs.recommended, ...tseslint.configs.recommended, )
  17. RainerHahnekamp Config const plugin = { configs: { recommended: {

    plugins: { custom: { rules: { 'capitalized-classes': capitalizeClasses } } }, rules: { 'custom/capitalized-classes': 'error', '@typescript-eslint/no-unused-vars': 'off' } } } } export = plugin
  18. RainerHahnekamp Where to go from here? • ESQuery ◦ https://ts-ast-viewer.com/

    • Testing • Customizations • Fixes • Processors ◦ Angular Template ◦ CSS ◦ ...