Linting Angular

Linting Angular

Following best practices is essential for the final success of any project. Unfortunately, the process of enforcing them is manual and error-prone - through code reviews. In the talk I will show how to automatically encourage best practices and verify common style by using static code analysis with codelyzer.

82bafb0432ce4ccc9dcc26f94d5fe5bc?s=128

Minko Gechev

March 10, 2017
Tweet

Transcript

  1. twitter.com/mgechev github.com/mgechev blog.mgechev.com Linting Angular

  2. None
  3. twitter.com/mgechev github.com/mgechev

  4. None
  5. None
  6. Agenda History Codelyzer How codelyzer works? Compilers 101 Future plans

  7. History

  8. None
  9. None
  10. None
  11. Applying The Style Guide In Your Project

  12. Enforcing Style

  13. Enforcing Style • Fork the official style guide • Modify

    the styles according to your needs Introduce the style guide to your team Verify that each individual code change follows it
  14. Enforcing Style • Fork the official style guide • Modify

    the styles according to your needs • Introduce the style guide to your team Verify that each individual code change follows it
  15. Enforcing Style • Fork the official style guide • Modify

    the styles according to your needs • Introduce the style guide to your team • Verify that each individual code change follows it
  16. Core Review Process

  17. None
  18. ‣ Manual ‣ Boring ‣ Error Prone

  19. codelyzer

  20. Codelyzer is a project which aims to enforce common style

    using static code analysis
  21. Codelyzer Rules • Components / directives name suffix • Components

    / directives selectors • Implement life cycle hook interfaces • Bind to public members • No dead CSS styles • …
  22. Codelyzer Rules Components / directives name suffix Components / directives

    selectors Implement life cycle hook interfaces Bind to public members No dead CSS styles …
  23. Codelyzer Rules Components / directives name suffix Components / directives

    selectors Implement life cycle hook interfaces Bind to public members No dead CSS styles …
  24. Codelyzer Rules Components / directives name suffix Components / directives

    selectors Implement life cycle hook interfaces Bind to public members No dead CSS styles …
  25. Codelyzer Rules Components / directives name suffix Components / directives

    selectors Implement life cycle hook interfaces Bind to public members No dead CSS styles …
  26. Codelyzer Rules Components / directives name suffix Components / directives

    selectors Implement life cycle hook interfaces Bind to public members No dead CSS styles …
  27. Codelyzer Rules Components / directives name suffix Components / directives

    selectors Implement life cycle hook interfaces Bind to public members No dead CSS styles …
  28. None
  29. None
  30. How Codelyzer Works?

  31. Compiler COMPILER Input Output

  32. Compiler COMPILER
 
 
 
 Input Output FRONT-END BACK-END

  33. FRONT-END Compiler Input Output

  34. FRONT-END
 
 
 
 Compiler Input Output LEXICAL ANALYSIS SYNTAX

    ANALYSIS … SEMANTIC ANALYSIS
  35. Lexical Analysis tokenize('const product = a * b;')

  36. Lexical Analysis tokenize('const product = a * b;')

  37. Lexical Analysis tokenize('const product = a * b;') [ {

    lexeme: 'const', type: 'keyword' }, { lexeme: 'product', type: 'identifier' }, { lexeme: '=', type: 'operator' }, ... ]
  38. Syntax Analysis parse(tokenize('...'))

  39. Syntax Analysis parse(tokenize('...'))

  40. Syntax Analysis parse(tokenize('...')) CONST PRODUCT * A B

  41. Syntax Analysis parse(tokenize('...')) CONST PRODUCT * A B AST

  42. Semantic Analysis typeCheck(parse(tokenize('...')))

  43. checkConst(node) { return checkExpression(node.initializer) } checkExpression(node) { if (node instanceof

    BinOp) { return checkBinOperation(node); } ... } checkBinOperation(node) { if (isMultiplication(node)) { if (!isNumber(node.left) || !isNumber(node.right)) { throw new Error("Node must be 'number'"); } ... } }
  44. checkConst(node) checkExpression(node) checkBinOperation(node) isNumber(node) CONST PRODUCT * A B

  45. checkConst(node) checkExpression(node) checkBinOperation(node) isNumber(node) CONST PRODUCT * A B

  46. checkConst(node) checkExpression(node) checkBinOperation(node) isNumber(node) CONST PRODUCT * A B

  47. checkConst(node) checkExpression(node) checkBinOperation(node) isNumber(node) CONST PRODUCT * A B

  48. checkConst(node) checkExpression(node) checkBinOperation(node) isNumber(node) CONST PRODUCT * A B

  49. checkConst(node) checkExpression(node) checkBinOperation(node) isNumber(node) CONST PRODUCT * A B

  50. checkConst(node) checkExpression(node) checkBinOperation(node) isNumber(node) CONST PRODUCT * A B

  51. None
  52. Static program analysis is the analysis of computer software that

    is performed without actually executing programs. Wikipedia
  53. codelyzer

  54. Codelyzer Goals • Analyze the source code statically • Show

    warning when a style is violated • Extensible • Reuse as much as possible
  55. tsc

  56. tslint

  57. Scott Wu Alex Eagle Martin Probst

  58. None
  59. tsc.parse( tokenize(` @Component({ selector: '[tooltip]', template: '...' }) class TooltipComponent

    { ... } `) );
  60. ast = tsc.parse( tsc.tokenize(` @Component({ selector: '[tooltip]', template: '...' })

    class TooltipComponent { ... } `) );
  61. ast = tsc.parse( tsc.tokenize(` @Component({ selector: '[tooltip]', template: '...' })

    class TooltipComponent { ... } `) );
  62. tokenize(` @Component({ selector: '[tooltip]', template: '...' }) class TooltipComponent {

    ... } `) );
  63. tokenize(` @Component({ selector: '[tooltip]', template: '...' }) class TooltipComponent {

    ... } `) ); CLASS DECORATORS[0] OBJ LITERAL SELECTOR [TOOLTIP] TEMPLATE ‘…’ PROPERTY[0] PROPERTY[1]
  64. Validate Selector visitClass(node) { node.decorators.forEach(visitDecorator); } visitDecorator(node) { if (node.name

    === 'Component') { visitComponentMetadata(readMetadata(node)) } } visitComponentMetadata(metadata) { if (!parseSelector(metadata.selector).element) { tslint.report('Components should have element selectors'); } }
  65. Validate Selector visitClass(node) visitDecorator(node) visitComponentMetadata(metadata) CLASS DECORATORS[0] OBJ LITERAL SELECTOR

    [TOOLTIP] TEMPLATE ‘…’ PROPERTY[0] PROPERTY[1]
  66. Validate Selector visitClass(node) visitDecorator(node) visitComponentMetadata(metadata) CLASS DECORATORS[0] OBJ LITERAL SELECTOR

    [TOOLTIP] TEMPLATE ‘…’ PROPERTY[0] PROPERTY[1]
  67. Validate Selector visitClass(node) visitDecorator(node) visitComponentMetadata(metadata) CLASS DECORATORS[0] OBJ LITERAL SELECTOR

    [TOOLTIP] TEMPLATE ‘…’ PROPERTY[0] PROPERTY[1]
  68. Validate Selector visitClass(node) visitDecorator(node) visitComponentMetadata(metadata) CLASS DECORATORS[0] OBJ LITERAL SELECTOR

    [TOOLTIP] TEMPLATE ‘…’ PROPERTY[0] PROPERTY[1]
  69. Validate Selector visitClass(node) visitDecorator(node) visitComponentMetadata(metadata) CLASS DECORATORS[0] OBJ LITERAL SELECTOR

    [TOOLTIP] TEMPLATE ‘…’ PROPERTY[0] PROPERTY[1]
  70. No Dead CSS

  71. @Component({ selector: 'header-cmp', template: ` <header> <h1>{{ title }}</h1> <div

    class="greeting">Hello <span>{{ name }}</span>!</div> </header> `, styles: [` h1 { font-size: 25px; } .greeting spam { color: red; } `] }) class HeaderComponent { ... }
  72. @Component({ selector: 'header-cmp', template: ` <header> <h1>{{ title }}</h1> <div

    class="greeting">Hello <span>{{ name }}</span>!</div> </header> `, styles: [` h1 { font-size: 25px; } .greeting spam { color: red; } `] }) class HeaderComponent { ... }
  73. CLASS DECORATORS[0] OBJ LITERAL SELECTOR HEADER-CMP TEMPLATE ‘…’ PROPERTY[0] PROPERTY[1]

    PROPERTY[2] [‘…’] STYLES
  74. CLASS DECORATORS[0] OBJ LITERAL SELECTOR HEADER-CMP TEMPLATE ‘…’ PROPERTY[0] PROPERTY[1]

    PROPERTY[2] [‘…’] STYLES
  75. Strings Are Hard To Analyze

  76. CLASS DECORATORS[0] OBJ LITERAL SELECTOR HEADER-CMP TEMPLATE TEMPLATE AST PROPERTY[0]

    PROPERTY[1] PROPERTY[2] STYLE ASTS STYLES ROOTS CSS RULES
  77. None
  78. None
  79. Current Limitations • Linting per file - not entire program

    • Doesn’t perform type checking • Will fail when selector is variable
  80. import { Component as NgComponent } from '@angular/core'; const selector

    = '[tool' + 'tip]'; @NgComponent({ selector, template: '...' }) class TooltipComponent { ... }
  81. import { Component as NgComponent } from '@angular/core'; const selector

    = '[tool' + 'tip]'; @NgComponent({ selector, template: '...' }) class TooltipComponent { ... }
  82. import { Component as NgComponent } from '@angular/core'; const selector

    = '[tool' + 'tip]'; @NgComponent({ selector, template: '...' }) class TooltipComponent { ... }
  83. ngast github.com/mgechev/ngast

  84. ngast • Provides high-level API to the compiler • Allows:

    • Deep metadata collection • Static evaluation of foldable expressions
  85. import { Component as NgComponent } from '@angular/core'; const selector

    = '[tool' + 'tip]'; @NgComponent({ selector, template: '...' }) class TooltipComponent { ... }
  86. import { Component as NgComponent } from '@angular/core'; const selector

    = '[tool' + 'tip]'; @NgComponent({ selector, template: '...' }) class TooltipComponent { ... } { "isHost": false, "isComponent": true, "selector": "[tooltip]", "changeDetection": 1, "inputs": {}, "outputs": {}, "template": { "template": "...", ... }, ... }
  87. Codelyzer 3 • Deep metadata collection • Autofixes • Much

    more!
  88. Thank you! twitter.com/mgechev github.com/mgechev blog.mgechev.com