diff --git a/packages/utils/src/lib/__snapshots__/report-to-md.integration.test.ts.snap b/packages/utils/src/lib/__snapshots__/report-to-md.integration.test.ts.snap index 33486e46b..fcefe9311 100644 --- a/packages/utils/src/lib/__snapshots__/report-to-md.integration.test.ts.snap +++ b/packages/utils/src/lib/__snapshots__/report-to-md.integration.test.ts.snap @@ -16,104 +16,103 @@ exports[`report-to-md > should contain all sections when using the fixture repor Performance metrics [📖 Docs](https://developers.google.com/web/fundamentals/performance) 🟢 Score: **92** - - 🟢 Performance (_Lighthouse_) - - 🟨 [First Contentful Paint](#first-contentful-paint-lighthouse) - **1.2 s** - - 🟨 [Largest Contentful Paint](#largest-contentful-paint-lighthouse) - **1.5 s** - - 🟩 [Speed Index](#speed-index-lighthouse) - **1.2 s** - 🟩 [Total Blocking Time](#total-blocking-time-lighthouse) - **0 ms** + - 🟨 [Largest Contentful Paint](#largest-contentful-paint-lighthouse) - **1.5 s** - 🟩 [Cumulative Layout Shift](#cumulative-layout-shift-lighthouse) - **0** -- 🟥 [Disallow missing \`key\` props in iterators/collection literals](#disallow-missing-key-props-in-iterators-collection-literals-eslint) (_ESLint_) - **1 warning** + - 🟨 [First Contentful Paint](#first-contentful-paint-lighthouse) - **1.2 s** + - 🟩 [Speed Index](#speed-index-lighthouse) - **1.2 s** +- 🟥 [Disallow missing \`key\` props in iterators/collection literals](#disallow-missing-key-props-in-iterators-collection-literals-eslint) (_ESLint_) - **1 warning** ### Bug prevention 🟡 Score: **68** +- 🟥 [verifies the list of dependencies for Hooks like useEffect and similar](#verifies-the-list-of-dependencies-for-hooks-like-useeffect-and-similar-eslint) (_ESLint_) - **2 warnings** +- 🟥 [Disallow missing \`key\` props in iterators/collection literals](#disallow-missing-key-props-in-iterators-collection-literals-eslint) (_ESLint_) - **1 warning** +- 🟩 [enforces the Rules of Hooks](#enforces-the-rules-of-hooks-eslint) (_ESLint_) - **0** +- 🟥 [Disallow missing props validation in a React component definition](#disallow-missing-props-validation-in-a-react-component-definition-eslint) (_ESLint_) - **6 warnings** +- 🟥 [Require the use of \`===\` and \`!==\`](#require-the-use-of--and--eslint) (_ESLint_) - **1 warning** - 🟩 [Disallow assignment operators in conditional expressions](#disallow-assignment-operators-in-conditional-expressions-eslint) (_ESLint_) - **0** -- 🟩 [Disallow reassigning \`const\` variables](#disallow-reassigning-const-variables-eslint) (_ESLint_) - **0** -- 🟩 [Disallow the use of \`debugger\`](#disallow-the-use-of-debugger-eslint) (_ESLint_) - **0** - 🟩 [Disallow invalid regular expression strings in \`RegExp\` constructors](#disallow-invalid-regular-expression-strings-in-regexp-constructors-eslint) (_ESLint_) - **0** -- 🟩 [Disallow the use of undeclared variables unless mentioned in \`/*global */\` comments](#disallow-the-use-of-undeclared-variables-unless-mentioned-in--global---comments-eslint) (_ESLint_) - **0** - 🟩 [Disallow loops with a body that allows only one iteration](#disallow-loops-with-a-body-that-allows-only-one-iteration-eslint) (_ESLint_) - **0** +- 🟩 [Disallow missing React when using JSX](#disallow-missing-react-when-using-jsx-eslint) (_ESLint_) - **0** - 🟩 [Disallow negating the left operand of relational operators](#disallow-negating-the-left-operand-of-relational-operators-eslint) (_ESLint_) - **0** +- 🟩 [Disallow reassigning \`const\` variables](#disallow-reassigning-const-variables-eslint) (_ESLint_) - **0** +- 🟩 [Disallow the use of \`debugger\`](#disallow-the-use-of-debugger-eslint) (_ESLint_) - **0** +- 🟩 [Disallow the use of undeclared variables unless mentioned in \`/*global */\` comments](#disallow-the-use-of-undeclared-variables-unless-mentioned-in--global---comments-eslint) (_ESLint_) - **0** - 🟩 [Disallow use of optional chaining in contexts where the \`undefined\` value is not allowed](#disallow-use-of-optional-chaining-in-contexts-where-the-undefined-value-is-not-allowed-eslint) (_ESLint_) - **0** -- 🟩 [Require calls to \`isNaN()\` when checking for \`NaN\`](#require-calls-to-isnan-when-checking-for-nan-eslint) (_ESLint_) - **0** - 🟩 [Enforce comparing \`typeof\` expressions against valid strings](#enforce-comparing-typeof-expressions-against-valid-strings-eslint) (_ESLint_) - **0** -- 🟥 [Require the use of \`===\` and \`!==\`](#require-the-use-of--and--eslint) (_ESLint_) - **1 warning** -- 🟥 [Disallow missing \`key\` props in iterators/collection literals](#disallow-missing-key-props-in-iterators-collection-literals-eslint) (_ESLint_) - **1 warning** -- 🟥 [Disallow missing props validation in a React component definition](#disallow-missing-props-validation-in-a-react-component-definition-eslint) (_ESLint_) - **6 warnings** -- 🟩 [Disallow missing React when using JSX](#disallow-missing-react-when-using-jsx-eslint) (_ESLint_) - **0** -- 🟩 [enforces the Rules of Hooks](#enforces-the-rules-of-hooks-eslint) (_ESLint_) - **0** -- 🟥 [verifies the list of dependencies for Hooks like useEffect and similar](#verifies-the-list-of-dependencies-for-hooks-like-useeffect-and-similar-eslint) (_ESLint_) - **2 warnings** - +- 🟩 [Require calls to \`isNaN()\` when checking for \`NaN\`](#require-calls-to-isnan-when-checking-for-nan-eslint) (_ESLint_) - **0** ### Code style 🟡 Score: **54** +- 🟥 [Require or disallow method and property shorthand syntax for object literals](#require-or-disallow-method-and-property-shorthand-syntax-for-object-literals-eslint) (_ESLint_) - **3 warnings** - 🟥 [Disallow unused variables](#disallow-unused-variables-eslint) (_ESLint_) - **1 warning** -- 🟥 [Require braces around arrow function bodies](#require-braces-around-arrow-function-bodies-eslint) (_ESLint_) - **1 warning** -- 🟩 [Enforce camelcase naming convention](#enforce-camelcase-naming-convention-eslint) (_ESLint_) - **0** -- 🟩 [Enforce consistent brace style for all control statements](#enforce-consistent-brace-style-for-all-control-statements-eslint) (_ESLint_) - **0** -- 🟥 [Require the use of \`===\` and \`!==\`](#require-the-use-of--and--eslint) (_ESLint_) - **1 warning** - 🟥 [Enforce a maximum number of lines of code in a function](#enforce-a-maximum-number-of-lines-of-code-in-a-function-eslint) (_ESLint_) - **1 warning** -- 🟩 [Enforce a maximum number of lines per file](#enforce-a-maximum-number-of-lines-per-file-eslint) (_ESLint_) - **0** -- 🟥 [Require or disallow method and property shorthand syntax for object literals](#require-or-disallow-method-and-property-shorthand-syntax-for-object-literals-eslint) (_ESLint_) - **3 warnings** -- 🟩 [Require using arrow functions for callbacks](#require-using-arrow-functions-for-callbacks-eslint) (_ESLint_) - **0** - 🟥 [Require \`const\` declarations for variables that are never reassigned after declared](#require-const-declarations-for-variables-that-are-never-reassigned-after-declared-eslint) (_ESLint_) - **1 warning** +- 🟥 [Require braces around arrow function bodies](#require-braces-around-arrow-function-bodies-eslint) (_ESLint_) - **1 warning** +- 🟥 [Require the use of \`===\` and \`!==\`](#require-the-use-of--and--eslint) (_ESLint_) - **1 warning** - 🟩 [Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead](#disallow-using-objectassign-with-an-object-literal-as-the-first-argument-and-prefer-the-use-of-object-spread-instead-eslint) (_ESLint_) - **0** -- 🟩 [Require or disallow \\"Yoda\\" conditions](#require-or-disallow-yoda-conditions-eslint) (_ESLint_) - **0** +- 🟩 [Enforce a maximum number of lines per file](#enforce-a-maximum-number-of-lines-per-file-eslint) (_ESLint_) - **0** +- 🟩 [Enforce camelcase naming convention](#enforce-camelcase-naming-convention-eslint) (_ESLint_) - **0** +- 🟩 [Enforce consistent brace style for all control statements](#enforce-consistent-brace-style-for-all-control-statements-eslint) (_ESLint_) - **0** - 🟩 [Require \`let\` or \`const\` instead of \`var\`](#require-let-or-const-instead-of-var-eslint) (_ESLint_) - **0** - +- 🟩 [Require or disallow \\"Yoda\\" conditions](#require-or-disallow-yoda-conditions-eslint) (_ESLint_) - **0** +- 🟩 [Require using arrow functions for callbacks](#require-using-arrow-functions-for-callbacks-eslint) (_ESLint_) - **0** ## 🛡️ Audits -### Disallow assignment operators in conditional expressions (ESLint) - -🟩 0 (score: 100) - -ESLint rule **no-cond-assign**. [📖 Docs](https://eslint.org/docs/latest/rules/no-cond-assign) - -### Disallow reassigning \`const\` variables (ESLint) - -🟩 0 (score: 100) +### Disallow missing props validation in a React component definition (ESLint) -ESLint rule **no-const-assign**. [📖 Docs](https://eslint.org/docs/latest/rules/no-const-assign) +
+🟥 6 warnings (score: 0) +

Issues

SeverityMessageSource fileLine(s)
⚠️ warning'onCreate' is missing in props validationsrc/components/CreateTodo.jsx15
⚠️ warning'setQuery' is missing in props validationsrc/components/TodoFilter.jsx10
⚠️ warning'setHideComplete' is missing in props validationsrc/components/TodoFilter.jsx18
⚠️ warning'todos' is missing in props validationsrc/components/TodoList.jsx6
⚠️ warning'todos.map' is missing in props validationsrc/components/TodoList.jsx6
⚠️ warning'onEdit' is missing in props validationsrc/components/TodoList.jsx13
+
-### Disallow the use of \`debugger\` (ESLint) -🟩 0 (score: 100) +ESLint rule **prop-types**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/prop-types.md) -ESLint rule **no-debugger**. [📖 Docs](https://eslint.org/docs/latest/rules/no-debugger) +### Disallow variable declarations from shadowing variables declared in the outer scope (ESLint) -### Disallow invalid regular expression strings in \`RegExp\` constructors (ESLint) +
+🟥 3 warnings (score: 0) +

Issues

SeverityMessageSource fileLine(s)
⚠️ warning'data' is already declared in the upper scope on line 5 column 10.src/hooks/useTodos.js11
⚠️ warning'data' is already declared in the upper scope on line 5 column 10.src/hooks/useTodos.js29
⚠️ warning'data' is already declared in the upper scope on line 5 column 10.src/hooks/useTodos.js41
+
-🟩 0 (score: 100) -ESLint rule **no-invalid-regexp**. [📖 Docs](https://eslint.org/docs/latest/rules/no-invalid-regexp) +ESLint rule **no-shadow**. [📖 Docs](https://eslint.org/docs/latest/rules/no-shadow) -### Disallow the use of undeclared variables unless mentioned in \`/*global */\` comments (ESLint) +### Require or disallow method and property shorthand syntax for object literals (ESLint) -🟩 0 (score: 100) +
+🟥 3 warnings (score: 0) +

Issues

SeverityMessageSource fileLine(s)
⚠️ warningExpected property shorthand.src/hooks/useTodos.js19
⚠️ warningExpected property shorthand.src/hooks/useTodos.js32
⚠️ warningExpected property shorthand.src/hooks/useTodos.js33
+
-ESLint rule **no-undef**. [📖 Docs](https://eslint.org/docs/latest/rules/no-undef) -### Disallow loops with a body that allows only one iteration (ESLint) +ESLint rule **object-shorthand**. [📖 Docs](https://eslint.org/docs/latest/rules/object-shorthand) -🟩 0 (score: 100) +### verifies the list of dependencies for Hooks like useEffect and similar (ESLint) -ESLint rule **no-unreachable-loop**. [📖 Docs](https://eslint.org/docs/latest/rules/no-unreachable-loop) +
+🟥 2 warnings (score: 0) +

Issues

SeverityMessageSource fileLine(s)
⚠️ warningReact Hook useCallback does nothing when called with only one argument. Did you forget to pass an array of dependencies?src/hooks/useTodos.js17
⚠️ warningReact Hook useCallback does nothing when called with only one argument. Did you forget to pass an array of dependencies?src/hooks/useTodos.js40
+
-### Disallow negating the left operand of relational operators (ESLint) -🟩 0 (score: 100) +ESLint rule **exhaustive-deps**, from _react-hooks_ plugin. [📖 Docs](https://github.com/facebook/react/issues/14920) -ESLint rule **no-unsafe-negation**. [📖 Docs](https://eslint.org/docs/latest/rules/no-unsafe-negation) +### Disallow missing \`key\` props in iterators/collection literals (ESLint) -### Disallow use of optional chaining in contexts where the \`undefined\` value is not allowed (ESLint) +
+🟥 1 warning (score: 0) +

Issues

SeverityMessageSource fileLine(s)
⚠️ warningMissing \\"key\\" prop for element in iteratorsrc/components/TodoList.jsx7-28
+
-🟩 0 (score: 100) -ESLint rule **no-unsafe-optional-chaining**. [📖 Docs](https://eslint.org/docs/latest/rules/no-unsafe-optional-chaining) +ESLint rule **jsx-key**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-key.md) ### Disallow unused variables (ESLint) @@ -125,17 +124,25 @@ ESLint rule **no-unsafe-optional-chaining**. [📖 Docs](https://eslint.org/docs ESLint rule **no-unused-vars**. [📖 Docs](https://eslint.org/docs/latest/rules/no-unused-vars) -### Require calls to \`isNaN()\` when checking for \`NaN\` (ESLint) +### Enforce a maximum number of lines of code in a function (ESLint) -🟩 0 (score: 100) +
+🟥 1 warning (score: 0) +

Issues

SeverityMessageSource fileLine(s)
⚠️ warningArrow function has too many lines (71). Maximum allowed is 50.src/hooks/useTodos.js3-73
+
-ESLint rule **use-isnan**. [📖 Docs](https://eslint.org/docs/latest/rules/use-isnan) -### Enforce comparing \`typeof\` expressions against valid strings (ESLint) +ESLint rule **max-lines-per-function**. [📖 Docs](https://eslint.org/docs/latest/rules/max-lines-per-function) -🟩 0 (score: 100) +### Require \`const\` declarations for variables that are never reassigned after declared (ESLint) + +
+🟥 1 warning (score: 0) +

Issues

SeverityMessageSource fileLine(s)
⚠️ warning'root' is never reassigned. Use 'const' instead.src/index.jsx5
+
-ESLint rule **valid-typeof**. [📖 Docs](https://eslint.org/docs/latest/rules/valid-typeof) + +ESLint rule **prefer-const**. [📖 Docs](https://eslint.org/docs/latest/rules/prefer-const) ### Require braces around arrow function bodies (ESLint) @@ -147,18 +154,6 @@ ESLint rule **valid-typeof**. [📖 Docs](https://eslint.org/docs/latest/rules/v ESLint rule **arrow-body-style**. [📖 Docs](https://eslint.org/docs/latest/rules/arrow-body-style) -### Enforce camelcase naming convention (ESLint) - -🟩 0 (score: 100) - -ESLint rule **camelcase**. [📖 Docs](https://eslint.org/docs/latest/rules/camelcase) - -### Enforce consistent brace style for all control statements (ESLint) - -🟩 0 (score: 100) - -ESLint rule **curly**. [📖 Docs](https://eslint.org/docs/latest/rules/curly) - ### Require the use of \`===\` and \`!==\` (ESLint)
@@ -169,153 +164,155 @@ ESLint rule **curly**. [📖 Docs](https://eslint.org/docs/latest/rules/curly) ESLint rule **eqeqeq**. [📖 Docs](https://eslint.org/docs/latest/rules/eqeqeq) -### Enforce a maximum number of lines of code in a function (ESLint) - -
-🟥 1 warning (score: 0) -

Issues

SeverityMessageSource fileLine(s)
⚠️ warningArrow function has too many lines (71). Maximum allowed is 50.src/hooks/useTodos.js3-73
-
+### Disallow \`target=\\"_blank\\"\` attribute without \`rel=\\"noreferrer\\"\` (ESLint) +🟩 0 (score: 100) -ESLint rule **max-lines-per-function**. [📖 Docs](https://eslint.org/docs/latest/rules/max-lines-per-function) +ESLint rule **jsx-no-target-blank**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-no-target-blank.md) -### Enforce a maximum number of lines per file (ESLint) +### Disallow assignment operators in conditional expressions (ESLint) 🟩 0 (score: 100) -ESLint rule **max-lines**. [📖 Docs](https://eslint.org/docs/latest/rules/max-lines) +ESLint rule **no-cond-assign**. [📖 Docs](https://eslint.org/docs/latest/rules/no-cond-assign) -### Disallow variable declarations from shadowing variables declared in the outer scope (ESLint) +### Disallow comments from being inserted as text nodes (ESLint) -
-🟥 3 warnings (score: 0) -

Issues

SeverityMessageSource fileLine(s)
⚠️ warning'data' is already declared in the upper scope on line 5 column 10.src/hooks/useTodos.js11
⚠️ warning'data' is already declared in the upper scope on line 5 column 10.src/hooks/useTodos.js29
⚠️ warning'data' is already declared in the upper scope on line 5 column 10.src/hooks/useTodos.js41
-
+🟩 0 (score: 100) +ESLint rule **jsx-no-comment-textnodes**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-no-comment-textnodes.md) -ESLint rule **no-shadow**. [📖 Docs](https://eslint.org/docs/latest/rules/no-shadow) +### Disallow direct mutation of this.state (ESLint) -### Require \`let\` or \`const\` instead of \`var\` (ESLint) +🟩 0 (score: 100) + +ESLint rule **no-direct-mutation-state**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-direct-mutation-state.md) + +### Disallow duplicate properties in JSX (ESLint) 🟩 0 (score: 100) -ESLint rule **no-var**. [📖 Docs](https://eslint.org/docs/latest/rules/no-var) +ESLint rule **jsx-no-duplicate-props**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-no-duplicate-props.md) -### Require or disallow method and property shorthand syntax for object literals (ESLint) +### Disallow invalid regular expression strings in \`RegExp\` constructors (ESLint) -
-🟥 3 warnings (score: 0) -

Issues

SeverityMessageSource fileLine(s)
⚠️ warningExpected property shorthand.src/hooks/useTodos.js19
⚠️ warningExpected property shorthand.src/hooks/useTodos.js32
⚠️ warningExpected property shorthand.src/hooks/useTodos.js33
-
+🟩 0 (score: 100) +ESLint rule **no-invalid-regexp**. [📖 Docs](https://eslint.org/docs/latest/rules/no-invalid-regexp) -ESLint rule **object-shorthand**. [📖 Docs](https://eslint.org/docs/latest/rules/object-shorthand) +### Disallow loops with a body that allows only one iteration (ESLint) -### Require using arrow functions for callbacks (ESLint) +🟩 0 (score: 100) + +ESLint rule **no-unreachable-loop**. [📖 Docs](https://eslint.org/docs/latest/rules/no-unreachable-loop) + +### Disallow missing displayName in a React component definition (ESLint) 🟩 0 (score: 100) -ESLint rule **prefer-arrow-callback**. [📖 Docs](https://eslint.org/docs/latest/rules/prefer-arrow-callback) +ESLint rule **display-name**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/display-name.md) -### Require \`const\` declarations for variables that are never reassigned after declared (ESLint) +### Disallow missing React when using JSX (ESLint) -
-🟥 1 warning (score: 0) -

Issues

SeverityMessageSource fileLine(s)
⚠️ warning'root' is never reassigned. Use 'const' instead.src/index.jsx5
-
+🟩 0 (score: 100) +ESLint rule **react-in-jsx-scope**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/react-in-jsx-scope.md) -ESLint rule **prefer-const**. [📖 Docs](https://eslint.org/docs/latest/rules/prefer-const) +### Disallow negating the left operand of relational operators (ESLint) -### Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead (ESLint) +🟩 0 (score: 100) + +ESLint rule **no-unsafe-negation**. [📖 Docs](https://eslint.org/docs/latest/rules/no-unsafe-negation) + +### Disallow passing of children as props (ESLint) 🟩 0 (score: 100) -ESLint rule **prefer-object-spread**. [📖 Docs](https://eslint.org/docs/latest/rules/prefer-object-spread) +ESLint rule **no-children-prop**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-children-prop.md) -### Require or disallow \\"Yoda\\" conditions (ESLint) +### Disallow React to be incorrectly marked as unused (ESLint) 🟩 0 (score: 100) -ESLint rule **yoda**. [📖 Docs](https://eslint.org/docs/latest/rules/yoda) +ESLint rule **jsx-uses-react**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-uses-react.md) -### Disallow missing \`key\` props in iterators/collection literals (ESLint) +### Disallow reassigning \`const\` variables (ESLint) -
-🟥 1 warning (score: 0) -

Issues

SeverityMessageSource fileLine(s)
⚠️ warningMissing \\"key\\" prop for element in iteratorsrc/components/TodoList.jsx7-28
-
+🟩 0 (score: 100) +ESLint rule **no-const-assign**. [📖 Docs](https://eslint.org/docs/latest/rules/no-const-assign) -ESLint rule **jsx-key**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-key.md) +### Disallow the use of \`debugger\` (ESLint) -### Disallow missing props validation in a React component definition (ESLint) +🟩 0 (score: 100) -
-🟥 6 warnings (score: 0) -

Issues

SeverityMessageSource fileLine(s)
⚠️ warning'onCreate' is missing in props validationsrc/components/CreateTodo.jsx15
⚠️ warning'setQuery' is missing in props validationsrc/components/TodoFilter.jsx10
⚠️ warning'setHideComplete' is missing in props validationsrc/components/TodoFilter.jsx18
⚠️ warning'todos' is missing in props validationsrc/components/TodoList.jsx6
⚠️ warning'todos.map' is missing in props validationsrc/components/TodoList.jsx6
⚠️ warning'onEdit' is missing in props validationsrc/components/TodoList.jsx13
-
+ESLint rule **no-debugger**. [📖 Docs](https://eslint.org/docs/latest/rules/no-debugger) +### Disallow the use of undeclared variables unless mentioned in \`/*global */\` comments (ESLint) -ESLint rule **prop-types**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/prop-types.md) +🟩 0 (score: 100) -### Disallow missing React when using JSX (ESLint) +ESLint rule **no-undef**. [📖 Docs](https://eslint.org/docs/latest/rules/no-undef) + +### Disallow undeclared variables in JSX (ESLint) 🟩 0 (score: 100) -ESLint rule **react-in-jsx-scope**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/react-in-jsx-scope.md) +ESLint rule **jsx-no-undef**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-no-undef.md) -### enforces the Rules of Hooks (ESLint) +### Disallow unescaped HTML entities from appearing in markup (ESLint) 🟩 0 (score: 100) -ESLint rule **rules-of-hooks**, from _react-hooks_ plugin. [📖 Docs](https://reactjs.org/docs/hooks-rules.html) +ESLint rule **no-unescaped-entities**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-unescaped-entities.md) -### verifies the list of dependencies for Hooks like useEffect and similar (ESLint) +### Disallow usage of deprecated methods (ESLint) -
-🟥 2 warnings (score: 0) -

Issues

SeverityMessageSource fileLine(s)
⚠️ warningReact Hook useCallback does nothing when called with only one argument. Did you forget to pass an array of dependencies?src/hooks/useTodos.js17
⚠️ warningReact Hook useCallback does nothing when called with only one argument. Did you forget to pass an array of dependencies?src/hooks/useTodos.js40
-
+🟩 0 (score: 100) +ESLint rule **no-deprecated**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-deprecated.md) -ESLint rule **exhaustive-deps**, from _react-hooks_ plugin. [📖 Docs](https://github.com/facebook/react/issues/14920) +### Disallow usage of findDOMNode (ESLint) -### Disallow missing displayName in a React component definition (ESLint) +🟩 0 (score: 100) + +ESLint rule **no-find-dom-node**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-find-dom-node.md) + +### Disallow usage of isMounted (ESLint) 🟩 0 (score: 100) -ESLint rule **display-name**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/display-name.md) +ESLint rule **no-is-mounted**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-is-mounted.md) -### Disallow comments from being inserted as text nodes (ESLint) +### Disallow usage of the return value of ReactDOM.render (ESLint) 🟩 0 (score: 100) -ESLint rule **jsx-no-comment-textnodes**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-no-comment-textnodes.md) +ESLint rule **no-render-return-value**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-render-return-value.md) -### Disallow duplicate properties in JSX (ESLint) +### Disallow usage of unknown DOM property (ESLint) 🟩 0 (score: 100) -ESLint rule **jsx-no-duplicate-props**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-no-duplicate-props.md) +ESLint rule **no-unknown-property**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-unknown-property.md) -### Disallow \`target=\\"_blank\\"\` attribute without \`rel=\\"noreferrer\\"\` (ESLint) +### Disallow use of optional chaining in contexts where the \`undefined\` value is not allowed (ESLint) 🟩 0 (score: 100) -ESLint rule **jsx-no-target-blank**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-no-target-blank.md) +ESLint rule **no-unsafe-optional-chaining**. [📖 Docs](https://eslint.org/docs/latest/rules/no-unsafe-optional-chaining) -### Disallow undeclared variables in JSX (ESLint) +### Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead (ESLint) 🟩 0 (score: 100) -ESLint rule **jsx-no-undef**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-no-undef.md) +ESLint rule **prefer-object-spread**. [📖 Docs](https://eslint.org/docs/latest/rules/prefer-object-spread) -### Disallow React to be incorrectly marked as unused (ESLint) +### Disallow using string references (ESLint) 🟩 0 (score: 100) -ESLint rule **jsx-uses-react**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-uses-react.md) +ESLint rule **no-string-refs**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-string-refs.md) ### Disallow variables used in JSX to be incorrectly marked as unused (ESLint) @@ -323,71 +320,71 @@ ESLint rule **jsx-uses-react**, from _react_ plugin. [📖 Docs](https://github. ESLint rule **jsx-uses-vars**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-uses-vars.md) -### Disallow passing of children as props (ESLint) +### Disallow when a DOM element is using both children and dangerouslySetInnerHTML (ESLint) 🟩 0 (score: 100) -ESLint rule **no-children-prop**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-children-prop.md) +ESLint rule **no-danger-with-children**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-danger-with-children.md) -### Disallow when a DOM element is using both children and dangerouslySetInnerHTML (ESLint) +### Enforce a maximum number of lines per file (ESLint) 🟩 0 (score: 100) -ESLint rule **no-danger-with-children**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-danger-with-children.md) +ESLint rule **max-lines**. [📖 Docs](https://eslint.org/docs/latest/rules/max-lines) -### Disallow usage of deprecated methods (ESLint) +### Enforce camelcase naming convention (ESLint) 🟩 0 (score: 100) -ESLint rule **no-deprecated**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-deprecated.md) +ESLint rule **camelcase**. [📖 Docs](https://eslint.org/docs/latest/rules/camelcase) -### Disallow direct mutation of this.state (ESLint) +### Enforce comparing \`typeof\` expressions against valid strings (ESLint) 🟩 0 (score: 100) -ESLint rule **no-direct-mutation-state**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-direct-mutation-state.md) +ESLint rule **valid-typeof**. [📖 Docs](https://eslint.org/docs/latest/rules/valid-typeof) -### Disallow usage of findDOMNode (ESLint) +### Enforce consistent brace style for all control statements (ESLint) 🟩 0 (score: 100) -ESLint rule **no-find-dom-node**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-find-dom-node.md) +ESLint rule **curly**. [📖 Docs](https://eslint.org/docs/latest/rules/curly) -### Disallow usage of isMounted (ESLint) +### Enforce ES5 or ES6 class for returning value in render function (ESLint) 🟩 0 (score: 100) -ESLint rule **no-is-mounted**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-is-mounted.md) +ESLint rule **require-render-return**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/require-render-return.md) -### Disallow usage of the return value of ReactDOM.render (ESLint) +### enforces the Rules of Hooks (ESLint) 🟩 0 (score: 100) -ESLint rule **no-render-return-value**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-render-return-value.md) +ESLint rule **rules-of-hooks**, from _react-hooks_ plugin. [📖 Docs](https://reactjs.org/docs/hooks-rules.html) -### Disallow using string references (ESLint) +### Require \`let\` or \`const\` instead of \`var\` (ESLint) 🟩 0 (score: 100) -ESLint rule **no-string-refs**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-string-refs.md) +ESLint rule **no-var**. [📖 Docs](https://eslint.org/docs/latest/rules/no-var) -### Disallow unescaped HTML entities from appearing in markup (ESLint) +### Require calls to \`isNaN()\` when checking for \`NaN\` (ESLint) 🟩 0 (score: 100) -ESLint rule **no-unescaped-entities**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-unescaped-entities.md) +ESLint rule **use-isnan**. [📖 Docs](https://eslint.org/docs/latest/rules/use-isnan) -### Disallow usage of unknown DOM property (ESLint) +### Require or disallow \\"Yoda\\" conditions (ESLint) 🟩 0 (score: 100) -ESLint rule **no-unknown-property**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-unknown-property.md) +ESLint rule **yoda**. [📖 Docs](https://eslint.org/docs/latest/rules/yoda) -### Enforce ES5 or ES6 class for returning value in render function (ESLint) +### Require using arrow functions for callbacks (ESLint) 🟩 0 (score: 100) -ESLint rule **require-render-return**, from _react_ plugin. [📖 Docs](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/require-render-return.md) +ESLint rule **prefer-arrow-callback**. [📖 Docs](https://eslint.org/docs/latest/rules/prefer-arrow-callback) ### First Contentful Paint (Lighthouse) @@ -401,11 +398,11 @@ ESLint rule **require-render-return**, from _react_ plugin. [📖 Docs](https:// [📖 Docs](https://developer.chrome.com/docs/lighthouse/performance/largest-contentful-paint/) -### Total Blocking Time (Lighthouse) +### Speed Index (Lighthouse) -🟩 0 ms (score: 100) +🟩 1.2 s (score: 93) -[📖 Docs](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-total-blocking-time/) +[📖 Docs](https://developer.chrome.com/docs/lighthouse/performance/speed-index/) ### Cumulative Layout Shift (Lighthouse) @@ -413,11 +410,11 @@ ESLint rule **require-render-return**, from _react_ plugin. [📖 Docs](https:// [📖 Docs](https://web.dev/cls/) -### Speed Index (Lighthouse) +### Total Blocking Time (Lighthouse) -🟩 1.2 s (score: 93) +🟩 0 ms (score: 100) -[📖 Docs](https://developer.chrome.com/docs/lighthouse/performance/speed-index/) +[📖 Docs](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-total-blocking-time/) diff --git a/packages/utils/src/lib/__snapshots__/report-to-stdout.integration.test.ts.snap b/packages/utils/src/lib/__snapshots__/report-to-stdout.integration.test.ts.snap index 0d55b3f4f..ab835a393 100644 --- a/packages/utils/src/lib/__snapshots__/report-to-stdout.integration.test.ts.snap +++ b/packages/utils/src/lib/__snapshots__/report-to-stdout.integration.test.ts.snap @@ -6,71 +6,71 @@ exports[`report-to-stdout > should contain all sections when using the fixture r ESLint audits -● Disallow assignment operators in conditional expressions 0 -● Disallow reassigning \`const\` variables 0 -● Disallow the use of \`debugger\` 0 -● Disallow invalid regular expression strings in \`RegExp\` 0 - constructors -● Disallow the use of undeclared variables unless mentioned in 0 - \`/*global */\` comments -● Disallow loops with a body that allows only one iteration 0 -● Disallow negating the left operand of relational operators 0 -● Disallow use of optional chaining in contexts where the 0 - \`undefined\` value is not allowed -● Disallow unused variables 1 warning -● Require calls to \`isNaN()\` when checking for \`NaN\` 0 -● Enforce comparing \`typeof\` expressions against valid strings 0 -● Require braces around arrow function bodies 1 warning -● Enforce camelcase naming convention 0 -● Enforce consistent brace style for all control statements 0 -● Require the use of \`===\` and \`!==\` 1 warning -● Enforce a maximum number of lines of code in a function 1 warning -● Enforce a maximum number of lines per file 0 +● Disallow missing props validation in a React component definition 6 warnings ● Disallow variable declarations from shadowing variables declared 3 warnings in the outer scope -● Require \`let\` or \`const\` instead of \`var\` 0 ● Require or disallow method and property shorthand syntax for 3 warnings object literals -● Require using arrow functions for callbacks 0 -● Require \`const\` declarations for variables that are never 1 warning - reassigned after declared -● Disallow using Object.assign with an object literal as the first 0 - argument and prefer the use of object spread instead -● Require or disallow \\"Yoda\\" conditions 0 -● Disallow missing \`key\` props in iterators/collection literals 1 warning -● Disallow missing props validation in a React component definition 6 warnings -● Disallow missing React when using JSX 0 -● enforces the Rules of Hooks 0 ● verifies the list of dependencies for Hooks like useEffect and 2 warnings similar -● Disallow missing displayName in a React component definition 0 +● Disallow missing \`key\` props in iterators/collection literals 1 warning +● Disallow unused variables 1 warning +● Enforce a maximum number of lines of code in a function 1 warning +● Require \`const\` declarations for variables that are never 1 warning + reassigned after declared +● Require braces around arrow function bodies 1 warning +● Require the use of \`===\` and \`!==\` 1 warning +● Disallow \`target=\\"_blank\\"\` attribute without \`rel=\\"noreferrer\\"\` 0 +● Disallow assignment operators in conditional expressions 0 ● Disallow comments from being inserted as text nodes 0 +● Disallow direct mutation of this.state 0 ● Disallow duplicate properties in JSX 0 -● Disallow \`target=\\"_blank\\"\` attribute without \`rel=\\"noreferrer\\"\` 0 -● Disallow undeclared variables in JSX 0 -● Disallow React to be incorrectly marked as unused 0 -● Disallow variables used in JSX to be incorrectly marked as unused 0 +● Disallow invalid regular expression strings in \`RegExp\` 0 + constructors +● Disallow loops with a body that allows only one iteration 0 +● Disallow missing displayName in a React component definition 0 +● Disallow missing React when using JSX 0 +● Disallow negating the left operand of relational operators 0 ● Disallow passing of children as props 0 -● Disallow when a DOM element is using both children and 0 - dangerouslySetInnerHTML +● Disallow React to be incorrectly marked as unused 0 +● Disallow reassigning \`const\` variables 0 +● Disallow the use of \`debugger\` 0 +● Disallow the use of undeclared variables unless mentioned in 0 + \`/*global */\` comments +● Disallow undeclared variables in JSX 0 +● Disallow unescaped HTML entities from appearing in markup 0 ● Disallow usage of deprecated methods 0 -● Disallow direct mutation of this.state 0 ● Disallow usage of findDOMNode 0 ● Disallow usage of isMounted 0 ● Disallow usage of the return value of ReactDOM.render 0 -● Disallow using string references 0 -● Disallow unescaped HTML entities from appearing in markup 0 ● Disallow usage of unknown DOM property 0 +● Disallow use of optional chaining in contexts where the 0 + \`undefined\` value is not allowed +● Disallow using Object.assign with an object literal as the first 0 + argument and prefer the use of object spread instead +● Disallow using string references 0 +● Disallow variables used in JSX to be incorrectly marked as unused 0 +● Disallow when a DOM element is using both children and 0 + dangerouslySetInnerHTML +● Enforce a maximum number of lines per file 0 +● Enforce camelcase naming convention 0 +● Enforce comparing \`typeof\` expressions against valid strings 0 +● Enforce consistent brace style for all control statements 0 ● Enforce ES5 or ES6 class for returning value in render function 0 +● enforces the Rules of Hooks 0 +● Require \`let\` or \`const\` instead of \`var\` 0 +● Require calls to \`isNaN()\` when checking for \`NaN\` 0 +● Require or disallow \\"Yoda\\" conditions 0 +● Require using arrow functions for callbacks 0 Lighthouse audits ● First Contentful Paint 1.2 s ● Largest Contentful Paint 1.5 s -● Total Blocking Time 0 ms -● Cumulative Layout Shift 0 ● Speed Index 1.2 s +● Cumulative Layout Shift 0 +● Total Blocking Time 0 ms Categories diff --git a/packages/utils/src/lib/report-to-md.ts b/packages/utils/src/lib/report-to-md.ts index ec3ca2b18..7034c43c4 100644 --- a/packages/utils/src/lib/report-to-md.ts +++ b/packages/utils/src/lib/report-to-md.ts @@ -1,8 +1,8 @@ import { AuditReport, CategoryConfig, + CategoryRef, Issue, - PluginReport, } from '@code-pushup/models'; import { CommitData } from './git'; import { @@ -24,6 +24,9 @@ import { detailsTableHeaders, formatDuration, formatReportScore, + getAuditByRef, + getGroupWithAudits, + getPluginNameFromSlug, getRoundScoreMarker, getSeverityIcon, getSquaredScoreMarker, @@ -31,8 +34,14 @@ import { reportHeadlineText, reportMetaTableHeaders, reportOverviewTableHeaders, + sortAudits, + sortCategoryAudits, } from './report'; -import { EnrichedScoredAuditGroup, ScoredReport } from './scoring'; +import { + EnrichedScoredAuditGroupWithAudits, + ScoredReport, + WeighedAuditReport, +} from './scoring'; import { slugify } from './transformation'; export function reportToMd( @@ -87,14 +96,37 @@ function reportToCategoriesSection(report: ScoredReport): string { )} Score: ${style(formatReportScore(category.score))}`; const categoryDocs = getDocsAndDescription(category); - const refs = category.refs.reduce((acc, ref) => { - if (ref.type === 'group') { - acc += groupRefItemToCategorySection(ref.slug, ref.plugin, plugins); - } else { - acc += auditRefItemToCategorySection(ref.slug, ref.plugin, plugins); - } - return acc; - }, ''); + const auditsAndGroups = category.refs.reduce( + ( + acc: { + audits: WeighedAuditReport[]; + groups: EnrichedScoredAuditGroupWithAudits[]; + }, + ref: CategoryRef, + ) => ({ + ...acc, + ...(ref.type === 'group' + ? { + groups: [ + ...acc.groups, + getGroupWithAudits(ref.slug, ref.plugin, plugins), + ], + } + : { + audits: [...acc.audits, getAuditByRef(ref, plugins)], + }), + }), + { groups: [], audits: [] }, + ); + + const audits = auditsAndGroups.audits + .sort(sortCategoryAudits) + .map(audit => auditItemToCategorySection(audit, plugins)) + .join(NEW_LINE); + + const groups = auditsAndGroups.groups + .map(group => groupItemToCategorySection(group, plugins)) + .join(''); return ( acc + @@ -105,73 +137,43 @@ function reportToCategoriesSection(report: ScoredReport): string { categoryDocs + categoryScore + NEW_LINE + + groups + NEW_LINE + - refs + audits ); }, ''); return h2('🏷 Categories') + NEW_LINE + categoryDetails; } -function auditRefItemToCategorySection( - refSlug: string, - refPlugin: string, +function auditItemToCategorySection( + audit: WeighedAuditReport, plugins: ScoredReport['plugins'], ): string { - const plugin = plugins.find(({ slug }) => slug === refPlugin) as PluginReport; - const pluginAudit = plugin?.audits.find(({ slug }) => slug === refSlug); - - if (!pluginAudit) { - throwIsNotPresentError(`Audit ${refSlug}`, plugin?.slug); - } - + const pluginTitle = getPluginNameFromSlug(audit.plugin, plugins); const auditTitle = link( - `#${slugify(pluginAudit.title)}-${slugify(plugin.title)}`, - pluginAudit?.title, + `#${slugify(audit.title)}-${slugify(pluginTitle)}`, + audit?.title, ); - - return ( - li( - `${getSquaredScoreMarker(pluginAudit.score)} ${auditTitle} (_${ - plugin.title - }_) - ${getAuditResult(pluginAudit)}`, - ) + NEW_LINE + return li( + `${getSquaredScoreMarker( + audit.score, + )} ${auditTitle} (_${pluginTitle}_) - ${getAuditResult(audit)}`, ); } -function groupRefItemToCategorySection( - refSlug: string, - refPlugin: string, +function groupItemToCategorySection( + group: EnrichedScoredAuditGroupWithAudits, plugins: ScoredReport['plugins'], ): string { - const plugin = plugins.find(({ slug }) => slug === refPlugin) as PluginReport; - const group = plugin?.groups?.find( - ({ slug }) => slug === refSlug, - ) as EnrichedScoredAuditGroup; + const pluginTitle = getPluginNameFromSlug(group.plugin, plugins); const groupScore = Number(formatReportScore(group?.score || 0)); - - if (!group) { - throwIsNotPresentError(`Group ${refSlug}`, plugin?.slug); - } - const groupTitle = li( - `${getRoundScoreMarker(groupScore)} ${group.title} (_${plugin.title}_)`, + `${getRoundScoreMarker(groupScore)} ${group.title} (_${pluginTitle}_)`, ); - const foundAudits = group.refs.reduce((acc, ref) => { - const audit = plugin?.audits.find( - ({ slug: auditSlugInPluginAudits }) => - auditSlugInPluginAudits === ref.slug, - ); - if (audit) { - return [...acc, audit]; - } - - return acc; - }, []); - - const groupAudits = foundAudits.reduce((acc, audit) => { + const groupAudits = group.audits.reduce((acc, audit) => { const auditTitle = link( - `#${slugify(audit.title)}-${slugify(plugin.title)}`, + `#${slugify(audit.title)}-${slugify(pluginTitle)}`, audit?.title, ); acc += ` ${li( @@ -187,9 +189,12 @@ function groupRefItemToCategorySection( } function reportToAuditsSection(report: ScoredReport): string { - const auditsData = report.plugins.reduce((acc, plugin) => { - const audits = plugin.audits.reduce((acc, audit) => { - const auditTitle = `${audit.title} (${plugin.title})`; + const auditsSection = report.plugins.reduce((acc, plugin) => { + const auditsData = plugin.audits.sort(sortAudits).reduce((acc, audit) => { + const auditTitle = `${audit.title} (${getPluginNameFromSlug( + audit.plugin, + report.plugins, + )})`; const detailsTitle = `${getSquaredScoreMarker( audit.score, )} ${getAuditResult(audit, true)} (score: ${formatReportScore( @@ -243,11 +248,10 @@ function reportToAuditsSection(report: ScoredReport): string { return acc; }, ''); - - return acc + audits; + return acc + auditsData; }, ''); - return h2('🛡️ Audits') + NEW_LINE + NEW_LINE + auditsData; + return h2('🛡️ Audits') + NEW_LINE + NEW_LINE + auditsSection; } function reportToAboutSection( @@ -325,7 +329,3 @@ function getAuditResult(audit: AuditReport, isHtml = false): string { ? `${displayValue || value}` : style(String(displayValue || value)); } - -function throwIsNotPresentError(itemName: string, presentPlace: string): never { - throw new Error(`${itemName} is not present in ${presentPlace}`); -} diff --git a/packages/utils/src/lib/report-to-stdout.ts b/packages/utils/src/lib/report-to-stdout.ts index 6aec8658e..7c636dafc 100644 --- a/packages/utils/src/lib/report-to-stdout.ts +++ b/packages/utils/src/lib/report-to-stdout.ts @@ -9,6 +9,7 @@ import { formatReportScore, reportHeadlineText, reportRawOverviewTableHeaders, + sortAudits, } from './report'; import { ScoredReport } from './scoring'; @@ -73,7 +74,7 @@ function reportToDetailSection(report: ScoredReport): string { const ui = cliui({ width: 80 }); - audits.forEach(({ score, title, displayValue, value }) => { + audits.sort(sortAudits).forEach(({ score, title, displayValue, value }) => { ui.div( { text: withColor({ score, text: '●' }), diff --git a/packages/utils/src/lib/report.ts b/packages/utils/src/lib/report.ts index 4709064b9..815c13bd8 100644 --- a/packages/utils/src/lib/report.ts +++ b/packages/utils/src/lib/report.ts @@ -13,7 +13,12 @@ import { readJsonFile, readTextFile, } from './file-system'; -import { ScoredReport } from './scoring'; +import { + EnrichedAuditReport, + EnrichedScoredAuditGroupWithAudits, + ScoredReport, + WeighedAuditReport, +} from './scoring'; import { pluralize } from './transformation'; export const FOOTER_PREFIX = 'Made with ❤ by'; // replace ❤️ with ❤, because of ❤️ has output issues @@ -161,6 +166,96 @@ export function countCategoryAudits( }, 0); } +export function getAuditByRef( + { slug, weight, plugin }: CategoryRef, + plugins: ScoredReport['plugins'], +): WeighedAuditReport { + const auditPlugin = plugins.find(({ slug }) => slug === plugin); + if (!auditPlugin) { + throwIsNotPresentError(`Plugin ${plugin}`, 'report'); + } + const audit = auditPlugin?.audits.find( + ({ slug: auditSlug }) => auditSlug === slug, + ); + if (!audit) { + throwIsNotPresentError(`Audit ${slug}`, auditPlugin?.slug); + } + return { + ...audit, + weight, + plugin, + }; +} + +export function getGroupWithAudits( + refSlug: string, + refPlugin: string, + plugins: ScoredReport['plugins'], +): EnrichedScoredAuditGroupWithAudits { + const plugin = plugins.find(({ slug }) => slug === refPlugin); + if (!plugin) { + throwIsNotPresentError(`Plugin ${refPlugin}`, 'report'); + } + const groupWithAudits = plugin?.groups?.find(({ slug }) => slug === refSlug); + + if (!groupWithAudits) { + throwIsNotPresentError(`Group ${refSlug}`, plugin?.slug); + } + const groupAudits = groupWithAudits.refs.reduce( + (acc: WeighedAuditReport[], ref) => { + const audit = getAuditByRef( + { ...ref, plugin: refPlugin } as CategoryRef, + plugins, + ); + if (audit) { + return [...acc, audit]; + } + return [...acc]; + }, + [], + ) as WeighedAuditReport[]; + const audits = groupAudits.sort(sortCategoryAudits); + + return { + ...groupWithAudits, + audits, + }; +} + +export function sortCategoryAudits( + a: WeighedAuditReport, + b: WeighedAuditReport, +): number { + if (a.weight !== b.weight) { + return b.weight - a.weight; + } + + if (a.score !== b.score) { + return a.score - b.score; + } + + if (a.value !== b.value) { + return b.value - a.value; + } + + return a.title.localeCompare(b.title); +} + +export function sortAudits( + a: EnrichedAuditReport, + b: EnrichedAuditReport, +): number { + if (a.score !== b.score) { + return a.score - b.score; + } + + if (a.value !== b.value) { + return b.value - a.value; + } + + return a.title.localeCompare(b.title); +} + export function compareIssueSeverity( severity1: CliIssueSeverity, severity2: CliIssueSeverity, @@ -192,3 +287,19 @@ export async function loadReport( // eslint-disable-next-line @typescript-eslint/no-explicit-any return readTextFile(filePath) as any; } + +export function throwIsNotPresentError( + itemName: string, + presentPlace: string, +): never { + throw new Error(`${itemName} is not present in ${presentPlace}`); +} + +export function getPluginNameFromSlug( + slug: string, + plugins: ScoredReport['plugins'], +): string { + return ( + plugins.find(({ slug: pluginSlug }) => pluginSlug === slug)?.title || slug + ); +} diff --git a/packages/utils/src/lib/report.unit.test.ts b/packages/utils/src/lib/report.unit.test.ts index dcf7a456f..464e3e1c1 100644 --- a/packages/utils/src/lib/report.unit.test.ts +++ b/packages/utils/src/lib/report.unit.test.ts @@ -1,5 +1,5 @@ import { vol } from 'memfs'; -import { afterEach, describe, expect, vi } from 'vitest'; +import { afterEach, describe, expect, it, vi } from 'vitest'; import { CategoryRef, IssueSeverity, PluginReport } from '@code-pushup/models'; import { MEMFS_VOLUME, report } from '@code-pushup/models/testing'; import { @@ -8,8 +8,16 @@ import { countWeightedRefs, formatBytes, formatCount, + getPluginNameFromSlug, loadReport, + sortAudits, + sortCategoryAudits, } from './report'; +import { + EnrichedAuditReport, + ScoredReport, + WeighedAuditReport, +} from './scoring'; // Mock file system API's vi.mock('fs', async () => { @@ -182,3 +190,98 @@ describe('loadReport', () => { ).rejects.toThrow('validation'); }); }); + +describe('sortCategoryAudits', () => { + it('should sort audits by weight and score', () => { + const mockAudits = [ + { weight: 0, score: 0.1 }, + { weight: 5, score: 1 }, + { weight: 0, score: 0.7 }, + { weight: 10, score: 1 }, + ] as WeighedAuditReport[]; + const sortedAudits = [...mockAudits].sort(sortCategoryAudits); + expect(sortedAudits).toEqual([ + { weight: 10, score: 1 }, + { weight: 5, score: 1 }, + { weight: 0, score: 0.1 }, + { weight: 0, score: 0.7 }, + ]); + }); + + it('should sort audits by score and value', () => { + const mockAudits = [ + { score: 0.7, value: 1 }, + { score: 1, value: 1 }, + { score: 0.7, value: 0 }, + { score: 0, value: 1 }, + ] as WeighedAuditReport[]; + const sortedAudits = [...mockAudits].sort(sortCategoryAudits); + expect(sortedAudits).toEqual([ + { score: 0, value: 1 }, + { score: 0.7, value: 1 }, + { score: 0.7, value: 0 }, + { score: 1, value: 1 }, + ]); + }); + + it('should sort audits by value and title', () => { + const mockAudits = [ + { value: 1, title: 'c' }, + { value: 0, title: 'b' }, + { value: 0, title: 'a' }, + { value: 1, title: 'd' }, + ] as WeighedAuditReport[]; + const sortedAudits = [...mockAudits].sort(sortCategoryAudits); + expect(sortedAudits).toEqual([ + { value: 1, title: 'c' }, + { value: 1, title: 'd' }, + { value: 0, title: 'a' }, + { value: 0, title: 'b' }, + ]); + }); +}); + +describe('sortAudits', () => { + it('should sort audits by score and value', () => { + const mockAudits = [ + { score: 0.7, value: 1 }, + { score: 1, value: 1 }, + { score: 0.7, value: 0 }, + { score: 0, value: 1 }, + ] as EnrichedAuditReport[]; + const sortedAudits = [...mockAudits].sort(sortAudits); + expect(sortedAudits).toEqual([ + { score: 0, value: 1 }, + { score: 0.7, value: 1 }, + { score: 0.7, value: 0 }, + { score: 1, value: 1 }, + ]); + }); + + it('should sort audits by value and title', () => { + const mockAudits: EnrichedAuditReport[] = [ + { value: 1, title: 'c' }, + { value: 0, title: 'b' }, + { value: 0, title: 'a' }, + { value: 1, title: 'd' }, + ] as EnrichedAuditReport[]; + const sortedAudits = [...mockAudits].sort(sortAudits); + expect(sortedAudits).toEqual([ + { value: 1, title: 'c' }, + { value: 1, title: 'd' }, + { value: 0, title: 'a' }, + { value: 0, title: 'b' }, + ]); + }); +}); + +describe('getPluginNameFromSlug', () => { + it('should return plugin name', () => { + const plugins = [ + { slug: 'plugin-a', title: 'Plugin A' }, + { slug: 'plugin-b', title: 'Plugin B' }, + ] as ScoredReport['plugins']; + expect(getPluginNameFromSlug('plugin-a', plugins)).toBe('Plugin A'); + expect(getPluginNameFromSlug('plugin-b', plugins)).toBe('Plugin B'); + }); +}); diff --git a/packages/utils/src/lib/scoring.ts b/packages/utils/src/lib/scoring.ts index e4ad1f061..fb89c4175 100644 --- a/packages/utils/src/lib/scoring.ts +++ b/packages/utils/src/lib/scoring.ts @@ -10,6 +10,10 @@ import { import { deepClone } from './transformation'; export type EnrichedAuditReport = AuditReport & { plugin: string }; +export type WeighedAuditReport = EnrichedAuditReport & { weight: number }; +export type EnrichedScoredAuditGroupWithAudits = EnrichedScoredAuditGroup & { + audits: AuditReport[]; +}; type ScoredCategoryConfig = CategoryConfig & { score: number }; export type EnrichedScoredAuditGroup = AuditGroup & {