diff --git a/e2e/cli-e2e/tests/__snapshots__/collect.spec.ts.snap b/e2e/cli-e2e/tests/__snapshots__/collect.spec.ts.snap index 324441d3a..10b4c0255 100644 --- a/e2e/cli-e2e/tests/__snapshots__/collect.spec.ts.snap +++ b/e2e/cli-e2e/tests/__snapshots__/collect.spec.ts.snap @@ -1,1007 +1,5 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`CLI collect > should create report.md 1`] = ` -{ - "categories": [ - { - "refs": [ - { - "plugin": "eslint", - "slug": "no-cond-assign", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "no-const-assign", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "no-debugger", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "no-invalid-regexp", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "no-undef", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "no-unreachable-loop", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "no-unsafe-negation", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "no-unsafe-optional-chaining", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "use-isnan", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "valid-typeof", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "eqeqeq", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "react-jsx-key", - "type": "audit", - "weight": 2, - }, - { - "plugin": "eslint", - "slug": "react-prop-types", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "react-react-in-jsx-scope", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "react-hooks-rules-of-hooks", - "type": "audit", - "weight": 2, - }, - { - "plugin": "eslint", - "slug": "react-hooks-exhaustive-deps", - "type": "audit", - "weight": 2, - }, - ], - "slug": "bug-prevention", - "title": "Bug prevention", - }, - { - "refs": [ - { - "plugin": "eslint", - "slug": "no-unused-vars", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "arrow-body-style", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "camelcase", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "curly", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "eqeqeq", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "max-lines-per-function", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "max-lines", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "object-shorthand", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "prefer-arrow-callback", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "prefer-const", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "prefer-object-spread", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "yoda", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "no-var", - "type": "audit", - "weight": 1, - }, - ], - "slug": "code-style", - "title": "Code style", - }, - ], - "packageName": "@code-pushup/core", - "plugins": [ - { - "audits": [ - { - "description": "ESLint rule **no-cond-assign**.", - "details": { - "issues": [], - }, - "docsUrl": "https://eslint.org/docs/latest/rules/no-cond-assign", - "score": 1, - "slug": "no-cond-assign", - "title": "Disallow assignment operators in conditional expressions", - "value": 0, - }, - { - "description": "ESLint rule **no-const-assign**.", - "details": { - "issues": [], - }, - "docsUrl": "https://eslint.org/docs/latest/rules/no-const-assign", - "score": 1, - "slug": "no-const-assign", - "title": "Disallow reassigning \`const\` variables", - "value": 0, - }, - { - "description": "ESLint rule **no-debugger**.", - "details": { - "issues": [], - }, - "docsUrl": "https://eslint.org/docs/latest/rules/no-debugger", - "score": 1, - "slug": "no-debugger", - "title": "Disallow the use of \`debugger\`", - "value": 0, - }, - { - "description": "ESLint rule **no-invalid-regexp**.", - "details": { - "issues": [], - }, - "docsUrl": "https://eslint.org/docs/latest/rules/no-invalid-regexp", - "score": 1, - "slug": "no-invalid-regexp", - "title": "Disallow invalid regular expression strings in \`RegExp\` constructors", - "value": 0, - }, - { - "description": "ESLint rule **no-undef**.", - "details": { - "issues": [], - }, - "docsUrl": "https://eslint.org/docs/latest/rules/no-undef", - "score": 1, - "slug": "no-undef", - "title": "Disallow the use of undeclared variables unless mentioned in \`/*global */\` comments", - "value": 0, - }, - { - "description": "ESLint rule **no-unreachable-loop**.", - "details": { - "issues": [], - }, - "docsUrl": "https://eslint.org/docs/latest/rules/no-unreachable-loop", - "score": 1, - "slug": "no-unreachable-loop", - "title": "Disallow loops with a body that allows only one iteration", - "value": 0, - }, - { - "description": "ESLint rule **no-unsafe-negation**.", - "details": { - "issues": [], - }, - "docsUrl": "https://eslint.org/docs/latest/rules/no-unsafe-negation", - "score": 1, - "slug": "no-unsafe-negation", - "title": "Disallow negating the left operand of relational operators", - "value": 0, - }, - { - "description": "ESLint rule **no-unsafe-optional-chaining**.", - "details": { - "issues": [], - }, - "docsUrl": "https://eslint.org/docs/latest/rules/no-unsafe-optional-chaining", - "score": 1, - "slug": "no-unsafe-optional-chaining", - "title": "Disallow use of optional chaining in contexts where the \`undefined\` value is not allowed", - "value": 0, - }, - { - "description": "ESLint rule **no-unused-vars**.", - "details": { - "issues": [ - { - "message": "'loading' is assigned a value but never used.", - "severity": "warning", - "source": { - "file": "src/App.jsx", - "position": { - "endColumn": 18, - "endLine": 8, - "startColumn": 11, - "startLine": 8, - }, - }, - }, - ], - }, - "displayValue": "1 warning", - "docsUrl": "https://eslint.org/docs/latest/rules/no-unused-vars", - "score": 0, - "slug": "no-unused-vars", - "title": "Disallow unused variables", - "value": 1, - }, - { - "description": "ESLint rule **use-isnan**.", - "details": { - "issues": [], - }, - "docsUrl": "https://eslint.org/docs/latest/rules/use-isnan", - "score": 1, - "slug": "use-isnan", - "title": "Require calls to \`isNaN()\` when checking for \`NaN\`", - "value": 0, - }, - { - "description": "ESLint rule **valid-typeof**.", - "details": { - "issues": [], - }, - "docsUrl": "https://eslint.org/docs/latest/rules/valid-typeof", - "score": 1, - "slug": "valid-typeof", - "title": "Enforce comparing \`typeof\` expressions against valid strings", - "value": 0, - }, - { - "description": "ESLint rule **arrow-body-style**.", - "details": { - "issues": [ - { - "message": "Unexpected block statement surrounding arrow body; move the returned value immediately after the \`=>\`.", - "severity": "warning", - "source": { - "file": "src/components/TodoFilter.jsx", - "position": { - "endColumn": 2, - "endLine": 25, - "startColumn": 29, - "startLine": 3, - }, - }, - }, - ], - }, - "displayValue": "1 warning", - "docsUrl": "https://eslint.org/docs/latest/rules/arrow-body-style", - "score": 0, - "slug": "arrow-body-style", - "title": "Require braces around arrow function bodies", - "value": 1, - }, - { - "description": "ESLint rule **camelcase**.", - "details": { - "issues": [], - }, - "docsUrl": "https://eslint.org/docs/latest/rules/camelcase", - "score": 1, - "slug": "camelcase", - "title": "Enforce camelcase naming convention", - "value": 0, - }, - { - "description": "ESLint rule **curly**.", - "details": { - "issues": [], - }, - "docsUrl": "https://eslint.org/docs/latest/rules/curly", - "score": 1, - "slug": "curly", - "title": "Enforce consistent brace style for all control statements", - "value": 0, - }, - { - "description": "ESLint rule **eqeqeq**.", - "details": { - "issues": [ - { - "message": "Expected '===' and instead saw '=='.", - "severity": "warning", - "source": { - "file": "src/hooks/useTodos.js", - "position": { - "endColumn": 43, - "endLine": 41, - "startColumn": 41, - "startLine": 41, - }, - }, - }, - ], - }, - "displayValue": "1 warning", - "docsUrl": "https://eslint.org/docs/latest/rules/eqeqeq", - "score": 0, - "slug": "eqeqeq", - "title": "Require the use of \`===\` and \`!==\`", - "value": 1, - }, - { - "description": "ESLint rule **max-lines-per-function**.", - "details": { - "issues": [ - { - "message": "Arrow function has too many lines (71). Maximum allowed is 50.", - "severity": "warning", - "source": { - "file": "src/hooks/useTodos.js", - "position": { - "endColumn": 2, - "endLine": 73, - "startColumn": 25, - "startLine": 3, - }, - }, - }, - ], - }, - "displayValue": "1 warning", - "docsUrl": "https://eslint.org/docs/latest/rules/max-lines-per-function", - "score": 0, - "slug": "max-lines-per-function", - "title": "Enforce a maximum number of lines of code in a function", - "value": 1, - }, - { - "description": "ESLint rule **max-lines**.", - "details": { - "issues": [], - }, - "docsUrl": "https://eslint.org/docs/latest/rules/max-lines", - "score": 1, - "slug": "max-lines", - "title": "Enforce a maximum number of lines per file", - "value": 0, - }, - { - "description": "ESLint rule **no-shadow**.", - "details": { - "issues": [ - { - "message": "'data' is already declared in the upper scope on line 5 column 10.", - "severity": "warning", - "source": { - "file": "src/hooks/useTodos.js", - "position": { - "endColumn": 17, - "endLine": 11, - "startColumn": 13, - "startLine": 11, - }, - }, - }, - { - "message": "'data' is already declared in the upper scope on line 5 column 10.", - "severity": "warning", - "source": { - "file": "src/hooks/useTodos.js", - "position": { - "endColumn": 21, - "endLine": 29, - "startColumn": 17, - "startLine": 29, - }, - }, - }, - { - "message": "'data' is already declared in the upper scope on line 5 column 10.", - "severity": "warning", - "source": { - "file": "src/hooks/useTodos.js", - "position": { - "endColumn": 17, - "endLine": 41, - "startColumn": 13, - "startLine": 41, - }, - }, - }, - ], - }, - "displayValue": "3 warnings", - "docsUrl": "https://eslint.org/docs/latest/rules/no-shadow", - "score": 0, - "slug": "no-shadow", - "title": "Disallow variable declarations from shadowing variables declared in the outer scope", - "value": 3, - }, - { - "description": "ESLint rule **no-var**.", - "details": { - "issues": [], - }, - "docsUrl": "https://eslint.org/docs/latest/rules/no-var", - "score": 1, - "slug": "no-var", - "title": "Require \`let\` or \`const\` instead of \`var\`", - "value": 0, - }, - { - "description": "ESLint rule **object-shorthand**.", - "details": { - "issues": [ - { - "message": "Expected property shorthand.", - "severity": "warning", - "source": { - "file": "src/hooks/useTodos.js", - "position": { - "endColumn": 19, - "endLine": 19, - "startColumn": 7, - "startLine": 19, - }, - }, - }, - { - "message": "Expected property shorthand.", - "severity": "warning", - "source": { - "file": "src/hooks/useTodos.js", - "position": { - "endColumn": 19, - "endLine": 32, - "startColumn": 13, - "startLine": 32, - }, - }, - }, - { - "message": "Expected property shorthand.", - "severity": "warning", - "source": { - "file": "src/hooks/useTodos.js", - "position": { - "endColumn": 25, - "endLine": 33, - "startColumn": 13, - "startLine": 33, - }, - }, - }, - ], - }, - "displayValue": "3 warnings", - "docsUrl": "https://eslint.org/docs/latest/rules/object-shorthand", - "score": 0, - "slug": "object-shorthand", - "title": "Require or disallow method and property shorthand syntax for object literals", - "value": 3, - }, - { - "description": "ESLint rule **prefer-arrow-callback**.", - "details": { - "issues": [], - }, - "docsUrl": "https://eslint.org/docs/latest/rules/prefer-arrow-callback", - "score": 1, - "slug": "prefer-arrow-callback", - "title": "Require using arrow functions for callbacks", - "value": 0, - }, - { - "description": "ESLint rule **prefer-const**.", - "details": { - "issues": [ - { - "message": "'root' is never reassigned. Use 'const' instead.", - "severity": "warning", - "source": { - "file": "src/index.jsx", - "position": { - "endColumn": 9, - "endLine": 5, - "startColumn": 5, - "startLine": 5, - }, - }, - }, - ], - }, - "displayValue": "1 warning", - "docsUrl": "https://eslint.org/docs/latest/rules/prefer-const", - "score": 0, - "slug": "prefer-const", - "title": "Require \`const\` declarations for variables that are never reassigned after declared", - "value": 1, - }, - { - "description": "ESLint rule **prefer-object-spread**.", - "details": { - "issues": [], - }, - "docsUrl": "https://eslint.org/docs/latest/rules/prefer-object-spread", - "score": 1, - "slug": "prefer-object-spread", - "title": "Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead", - "value": 0, - }, - { - "description": "ESLint rule **yoda**.", - "details": { - "issues": [], - }, - "docsUrl": "https://eslint.org/docs/latest/rules/yoda", - "score": 1, - "slug": "yoda", - "title": "Require or disallow \\"Yoda\\" conditions", - "value": 0, - }, - { - "description": "ESLint rule **jsx-key**, from _react_ plugin.", - "details": { - "issues": [ - { - "message": "Missing \\"key\\" prop for element in iterator", - "severity": "warning", - "source": { - "file": "src/components/TodoList.jsx", - "position": { - "endColumn": 12, - "endLine": 28, - "startColumn": 7, - "startLine": 7, - }, - }, - }, - ], - }, - "displayValue": "1 warning", - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-key.md", - "score": 0, - "slug": "react-jsx-key", - "title": "Disallow missing \`key\` props in iterators/collection literals", - "value": 1, - }, - { - "description": "ESLint rule **prop-types**, from _react_ plugin.", - "details": { - "issues": [ - { - "message": "'onCreate' is missing in props validation", - "severity": "warning", - "source": { - "file": "src/components/CreateTodo.jsx", - "position": { - "endColumn": 23, - "endLine": 15, - "startColumn": 15, - "startLine": 15, - }, - }, - }, - { - "message": "'setQuery' is missing in props validation", - "severity": "warning", - "source": { - "file": "src/components/TodoFilter.jsx", - "position": { - "endColumn": 25, - "endLine": 10, - "startColumn": 17, - "startLine": 10, - }, - }, - }, - { - "message": "'setHideComplete' is missing in props validation", - "severity": "warning", - "source": { - "file": "src/components/TodoFilter.jsx", - "position": { - "endColumn": 34, - "endLine": 18, - "startColumn": 19, - "startLine": 18, - }, - }, - }, - { - "message": "'todos' is missing in props validation", - "severity": "warning", - "source": { - "file": "src/components/TodoList.jsx", - "position": { - "endColumn": 17, - "endLine": 6, - "startColumn": 12, - "startLine": 6, - }, - }, - }, - { - "message": "'todos.map' is missing in props validation", - "severity": "warning", - "source": { - "file": "src/components/TodoList.jsx", - "position": { - "endColumn": 21, - "endLine": 6, - "startColumn": 18, - "startLine": 6, - }, - }, - }, - { - "message": "'onEdit' is missing in props validation", - "severity": "warning", - "source": { - "file": "src/components/TodoList.jsx", - "position": { - "endColumn": 27, - "endLine": 13, - "startColumn": 21, - "startLine": 13, - }, - }, - }, - ], - }, - "displayValue": "6 warnings", - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/prop-types.md", - "score": 0, - "slug": "react-prop-types", - "title": "Disallow missing props validation in a React component definition", - "value": 6, - }, - { - "description": "ESLint rule **react-in-jsx-scope**, from _react_ plugin.", - "details": { - "issues": [], - }, - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/react-in-jsx-scope.md", - "score": 1, - "slug": "react-react-in-jsx-scope", - "title": "Disallow missing React when using JSX", - "value": 0, - }, - { - "description": "ESLint rule **rules-of-hooks**, from _react-hooks_ plugin.", - "details": { - "issues": [], - }, - "docsUrl": "https://reactjs.org/docs/hooks-rules.html", - "score": 1, - "slug": "react-hooks-rules-of-hooks", - "title": "enforces the Rules of Hooks", - "value": 0, - }, - { - "description": "ESLint rule **exhaustive-deps**, from _react-hooks_ plugin.", - "details": { - "issues": [ - { - "message": "React Hook useCallback does nothing when called with only one argument. Did you forget to pass an array of dependencies?", - "severity": "warning", - "source": { - "file": "src/hooks/useTodos.js", - "position": { - "endColumn": 31, - "endLine": 17, - "startColumn": 20, - "startLine": 17, - }, - }, - }, - { - "message": "React Hook useCallback does nothing when called with only one argument. Did you forget to pass an array of dependencies?", - "severity": "warning", - "source": { - "file": "src/hooks/useTodos.js", - "position": { - "endColumn": 29, - "endLine": 40, - "startColumn": 18, - "startLine": 40, - }, - }, - }, - ], - }, - "displayValue": "2 warnings", - "docsUrl": "https://github.com/facebook/react/issues/14920", - "score": 0, - "slug": "react-hooks-exhaustive-deps", - "title": "verifies the list of dependencies for Hooks like useEffect and similar", - "value": 2, - }, - { - "description": "ESLint rule **display-name**, from _react_ plugin.", - "details": { - "issues": [], - }, - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/display-name.md", - "score": 1, - "slug": "react-display-name", - "title": "Disallow missing displayName in a React component definition", - "value": 0, - }, - { - "description": "ESLint rule **jsx-no-comment-textnodes**, from _react_ plugin.", - "details": { - "issues": [], - }, - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-no-comment-textnodes.md", - "score": 1, - "slug": "react-jsx-no-comment-textnodes", - "title": "Disallow comments from being inserted as text nodes", - "value": 0, - }, - { - "description": "ESLint rule **jsx-no-duplicate-props**, from _react_ plugin.", - "details": { - "issues": [], - }, - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-no-duplicate-props.md", - "score": 1, - "slug": "react-jsx-no-duplicate-props", - "title": "Disallow duplicate properties in JSX", - "value": 0, - }, - { - "description": "ESLint rule **jsx-no-target-blank**, from _react_ plugin.", - "details": { - "issues": [], - }, - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-no-target-blank.md", - "score": 1, - "slug": "react-jsx-no-target-blank", - "title": "Disallow \`target=\\"_blank\\"\` attribute without \`rel=\\"noreferrer\\"\`", - "value": 0, - }, - { - "description": "ESLint rule **jsx-no-undef**, from _react_ plugin.", - "details": { - "issues": [], - }, - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-no-undef.md", - "score": 1, - "slug": "react-jsx-no-undef", - "title": "Disallow undeclared variables in JSX", - "value": 0, - }, - { - "description": "ESLint rule **jsx-uses-react**, from _react_ plugin.", - "details": { - "issues": [], - }, - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-uses-react.md", - "score": 1, - "slug": "react-jsx-uses-react", - "title": "Disallow React to be incorrectly marked as unused", - "value": 0, - }, - { - "description": "ESLint rule **jsx-uses-vars**, from _react_ plugin.", - "details": { - "issues": [], - }, - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-uses-vars.md", - "score": 1, - "slug": "react-jsx-uses-vars", - "title": "Disallow variables used in JSX to be incorrectly marked as unused", - "value": 0, - }, - { - "description": "ESLint rule **no-children-prop**, from _react_ plugin.", - "details": { - "issues": [], - }, - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-children-prop.md", - "score": 1, - "slug": "react-no-children-prop", - "title": "Disallow passing of children as props", - "value": 0, - }, - { - "description": "ESLint rule **no-danger-with-children**, from _react_ plugin.", - "details": { - "issues": [], - }, - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-danger-with-children.md", - "score": 1, - "slug": "react-no-danger-with-children", - "title": "Disallow when a DOM element is using both children and dangerouslySetInnerHTML", - "value": 0, - }, - { - "description": "ESLint rule **no-deprecated**, from _react_ plugin.", - "details": { - "issues": [], - }, - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-deprecated.md", - "score": 1, - "slug": "react-no-deprecated", - "title": "Disallow usage of deprecated methods", - "value": 0, - }, - { - "description": "ESLint rule **no-direct-mutation-state**, from _react_ plugin.", - "details": { - "issues": [], - }, - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-direct-mutation-state.md", - "score": 1, - "slug": "react-no-direct-mutation-state", - "title": "Disallow direct mutation of this.state", - "value": 0, - }, - { - "description": "ESLint rule **no-find-dom-node**, from _react_ plugin.", - "details": { - "issues": [], - }, - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-find-dom-node.md", - "score": 1, - "slug": "react-no-find-dom-node", - "title": "Disallow usage of findDOMNode", - "value": 0, - }, - { - "description": "ESLint rule **no-is-mounted**, from _react_ plugin.", - "details": { - "issues": [], - }, - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-is-mounted.md", - "score": 1, - "slug": "react-no-is-mounted", - "title": "Disallow usage of isMounted", - "value": 0, - }, - { - "description": "ESLint rule **no-render-return-value**, from _react_ plugin.", - "details": { - "issues": [], - }, - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-render-return-value.md", - "score": 1, - "slug": "react-no-render-return-value", - "title": "Disallow usage of the return value of ReactDOM.render", - "value": 0, - }, - { - "description": "ESLint rule **no-string-refs**, from _react_ plugin.", - "details": { - "issues": [], - }, - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-string-refs.md", - "score": 1, - "slug": "react-no-string-refs", - "title": "Disallow using string references", - "value": 0, - }, - { - "description": "ESLint rule **no-unescaped-entities**, from _react_ plugin.", - "details": { - "issues": [], - }, - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-unescaped-entities.md", - "score": 1, - "slug": "react-no-unescaped-entities", - "title": "Disallow unescaped HTML entities from appearing in markup", - "value": 0, - }, - { - "description": "ESLint rule **no-unknown-property**, from _react_ plugin.", - "details": { - "issues": [], - }, - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/no-unknown-property.md", - "score": 1, - "slug": "react-no-unknown-property", - "title": "Disallow usage of unknown DOM property", - "value": 0, - }, - { - "description": "ESLint rule **require-render-return**, from _react_ plugin.", - "details": { - "issues": [], - }, - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/require-render-return.md", - "score": 1, - "slug": "react-require-render-return", - "title": "Enforce ES5 or ES6 class for returning value in render function", - "value": 0, - }, - ], - "description": "Official Code PushUp ESLint plugin", - "icon": "eslint", - "packageName": "@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - ], -} -`; - exports[`CLI collect > should run ESLint plugin and create report.json 1`] = ` { "categories": [ diff --git a/e2e/cli-e2e/tests/collect.spec.ts b/e2e/cli-e2e/tests/collect.spec.ts index 89fd8dd44..bc72c4767 100644 --- a/e2e/cli-e2e/tests/collect.spec.ts +++ b/e2e/cli-e2e/tests/collect.spec.ts @@ -1,3 +1,4 @@ +import { join } from 'path'; import { afterEach, beforeEach, vi } from 'vitest'; import { PluginReport, Report, reportSchema } from '@code-pushup/models'; import { executeProcess, readJsonFile, readTextFile } from '@code-pushup/utils'; @@ -21,10 +22,13 @@ describe('CLI collect', () => { plugins: report.plugins.map(omitVariableData) as PluginReport[], }); + const cliPath = join('..', '..', 'dist', 'packages', 'cli'); + beforeEach(async () => { vi.clearAllMocks(); cleanFolderPutGitKeep(); }); + afterEach(() => { cleanFolderPutGitKeep(); }); @@ -32,7 +36,7 @@ describe('CLI collect', () => { it('should run ESLint plugin and create report.json', async () => { const { code, stderr } = await executeProcess({ command: 'npx', - args: ['../../dist/packages/cli', 'collect'], + args: [cliPath, 'collect'], cwd: 'examples/react-todos-app', }); @@ -48,7 +52,7 @@ describe('CLI collect', () => { it('should create report.md', async () => { const { code, stderr } = await executeProcess({ command: 'npx', - args: ['../../dist/packages/cli', 'collect', '--persist.format=md'], + args: [cliPath, 'collect', '--persist.format=md'], cwd: 'examples/react-todos-app', }); @@ -65,12 +69,7 @@ describe('CLI collect', () => { it('should print report summary to stdout', async () => { const { code, stdout, stderr } = await executeProcess({ command: 'npx', - args: [ - '../../dist/packages/cli', - 'collect', - '--verbose', - '--persist.format=stdout', - ], + args: [cliPath, 'collect', '--verbose', '--persist.format=stdout'], cwd: 'examples/react-todos-app', }); diff --git a/packages/cli/code-pushup.config.ts b/packages/cli/code-pushup.config.mock.ts similarity index 70% rename from packages/cli/code-pushup.config.ts rename to packages/cli/code-pushup.config.mock.ts index 52ca0f539..44e4429c8 100644 --- a/packages/cli/code-pushup.config.ts +++ b/packages/cli/code-pushup.config.mock.ts @@ -1,3 +1,6 @@ +import { join } from 'path'; +import { echoRunnerConfig } from '../models/test/fixtures/echo-runner-config.mock'; + const outputDir = 'tmp'; export default { @@ -37,35 +40,26 @@ export default { docsUrl: 'http://www.my-docs.dev?slug=dummy-audit-3', }, ], - runner: { - command: 'node', - args: [ - '-e', - `require('fs').writeFileSync('${outputDir}/dummy-plugin-output.json', '${JSON.stringify( - [ - { - title: 'Dummy Audit 1', - slug: 'dummy-audit-1', - value: 420, - score: 0.42, - }, - { - title: 'Dummy Audit 2', - slug: 'dummy-audit-2', - value: 80, - score: 0, - }, - { - title: 'Dummy Audit 3', - slug: 'dummy-audit-3', - value: 12, - score: 0.12, - }, - ], - )}')`, + runner: echoRunnerConfig( + [ + { + slug: 'dummy-audit-1', + value: 420, + score: 0.42, + }, + { + slug: 'dummy-audit-2', + value: 80, + score: 0, + }, + { + slug: 'dummy-audit-3', + value: 12, + score: 0.12, + }, ], - outputFile: `${outputDir}/dummy-plugin-output.json`, - }, + join(outputDir, 'dummy-plugin-output.json'), + ), }, ], categories: [ diff --git a/packages/cli/src/lib/implementation/config-middleware.spec.ts b/packages/cli/src/lib/implementation/config-middleware.spec.ts index f1bb6510e..0f7b1ee5a 100644 --- a/packages/cli/src/lib/implementation/config-middleware.spec.ts +++ b/packages/cli/src/lib/implementation/config-middleware.spec.ts @@ -42,7 +42,7 @@ describe('applyConfigMiddleware', () => { }); it('should throw with invalid config', async () => { - const invalidConfig = 'wrong/path/to/config'; + const invalidConfig = join('wrong', 'path', 'to', 'config'); let error: Error = new Error(); await configMiddleware({ config: invalidConfig }).catch(e => (error = e)); expect(error?.message).toContain(invalidConfig); diff --git a/packages/cli/test/fs.mock.ts b/packages/cli/test/fs.mock.ts index 6e28f9635..2bcf17baf 100644 --- a/packages/cli/test/fs.mock.ts +++ b/packages/cli/test/fs.mock.ts @@ -6,7 +6,7 @@ export function cleanFolder( content?: { [key in keyof T]: string }, ) { rmSync(dirName, { recursive: true, force: true }); - mkdirSync(dirName); + mkdirSync(dirName, { recursive: true }); if (content) { for (const fileName in content) { writeFileSync(join(dirName, fileName), content[fileName]); @@ -19,7 +19,7 @@ export function cleanFolderPutGitKeep( content?: { [key in keyof T]: string }, ) { rmSync(dirName, { recursive: true, force: true }); - mkdirSync(dirName); + mkdirSync(dirName, { recursive: true }); writeFileSync(join(dirName, '.gitkeep'), ''); if (content) { for (const fileName in content) { diff --git a/packages/core/src/lib/implementation/execute-plugin.spec.ts b/packages/core/src/lib/implementation/execute-plugin.spec.ts index 8652dd294..3581d1b88 100644 --- a/packages/core/src/lib/implementation/execute-plugin.spec.ts +++ b/packages/core/src/lib/implementation/execute-plugin.spec.ts @@ -1,3 +1,4 @@ +import { join } from 'path'; import { beforeEach, describe, expect, it } from 'vitest'; import { AuditReport, @@ -6,8 +7,8 @@ import { } from '@code-pushup/models'; import { auditReport, + echoRunnerConfig, pluginConfig, - runnerConfig, } from '@code-pushup/models/testing'; import { cleanFolder } from '../../../test'; import { executePlugin, executePlugins } from './execute-plugin'; @@ -45,7 +46,7 @@ describe('executePlugin', () => { { p: 42 } as unknown as AuditReport, ]; const pluginCfg = pluginConfig([auditReport()], { - runner: runnerConfig(invalidAuditOutputs), + runner: echoRunnerConfig(invalidAuditOutputs, join('tmp', 'out.json')), }); await expect(() => executePlugin(pluginCfg)).rejects.toThrowError( 'Plugin output of plugin with slug mock-plugin-slug', diff --git a/packages/core/test/fs.mock.ts b/packages/core/test/fs.mock.ts index 6e28f9635..2bcf17baf 100644 --- a/packages/core/test/fs.mock.ts +++ b/packages/core/test/fs.mock.ts @@ -6,7 +6,7 @@ export function cleanFolder( content?: { [key in keyof T]: string }, ) { rmSync(dirName, { recursive: true, force: true }); - mkdirSync(dirName); + mkdirSync(dirName, { recursive: true }); if (content) { for (const fileName in content) { writeFileSync(join(dirName, fileName), content[fileName]); @@ -19,7 +19,7 @@ export function cleanFolderPutGitKeep( content?: { [key in keyof T]: string }, ) { rmSync(dirName, { recursive: true, force: true }); - mkdirSync(dirName); + mkdirSync(dirName, { recursive: true }); writeFileSync(join(dirName, '.gitkeep'), ''); if (content) { for (const fileName in content) { diff --git a/packages/models/test/fixtures/code-pushup.config.mock.cjs b/packages/models/test/fixtures/code-pushup.config.mock.cjs index 806cd86ce..4d4011933 100644 --- a/packages/models/test/fixtures/code-pushup.config.mock.cjs +++ b/packages/models/test/fixtures/code-pushup.config.mock.cjs @@ -1,3 +1,6 @@ +import { join } from 'path'; +import { echoRunnerConfig } from './echo-runner-config.mock'; + const outputDir = 'tmp'; module.exports = { upload: { @@ -17,23 +20,16 @@ module.exports = { docsUrl: 'http://www.my-docs.dev', }, ], - runner: { - command: 'node', - args: [ - '-e', - `require('fs').writeFileSync('${outputDir}/out.json', '${JSON.stringify( - [ - { - title: 'dummy-title', - slug: 'command-object-audit-slug', - value: 0, - score: 0, - }, - ], - )}')`, + runner: echoRunnerConfig( + [ + { + slug: 'command-object-audit-slug', + value: 0, + score: 0, + }, ], - outputFile: `${outputDir}/out.json`, - }, + join(outputDir, 'out.json'), + ), groups: [], slug: 'command-object-plugin', title: 'command-object plugin', diff --git a/packages/models/test/fixtures/code-pushup.config.mock.js b/packages/models/test/fixtures/code-pushup.config.mock.js index d9b231317..b48a0eb68 100644 --- a/packages/models/test/fixtures/code-pushup.config.mock.js +++ b/packages/models/test/fixtures/code-pushup.config.mock.js @@ -1,3 +1,6 @@ +import { join } from 'path'; +import { echoRunnerConfig } from './echo-runner-config.mock'; + const outputDir = 'tmp'; export default { upload: { @@ -17,23 +20,16 @@ export default { docsUrl: 'http://www.my-docs.dev', }, ], - runner: { - command: 'node', - args: [ - '-e', - `require('fs').writeFileSync('${outputDir}/out.json', '${JSON.stringify( - [ - { - title: 'dummy-title', - slug: 'command-object-audit-slug', - value: 0, - score: 0, - }, - ], - )}')`, + runner: echoRunnerConfig( + [ + { + slug: 'command-object-audit-slug', + value: 0, + score: 0, + }, ], - outputFile: `${outputDir}/out.json`, - }, + join(outputDir, 'out.json'), + ), groups: [], slug: 'command-object-plugin', title: 'command-object plugin', diff --git a/packages/models/test/fixtures/code-pushup.config.mock.mjs b/packages/models/test/fixtures/code-pushup.config.mock.mjs index ead747273..7a5737fc0 100644 --- a/packages/models/test/fixtures/code-pushup.config.mock.mjs +++ b/packages/models/test/fixtures/code-pushup.config.mock.mjs @@ -1,3 +1,6 @@ +import { join } from 'path'; +import { echoRunnerConfig } from './echo-runner-config.mock'; + const outputDir = 'tmp'; export default { upload: { @@ -17,23 +20,16 @@ export default { docsUrl: 'http://www.my-docs.dev', }, ], - runner: { - command: 'node', - args: [ - '-e', - `require('fs').writeFileSync('${outputDir}/out.json', '${JSON.stringify( - [ - { - title: 'dummy-title', - slug: 'command-object-audit-slug', - value: 0, - score: 0, - }, - ], - )}')`, + runner: echoRunnerConfig( + [ + { + slug: 'command-object-audit-slug', + value: 0, + score: 0, + }, ], - outputFile: `${outputDir}/out.json`, - }, + join(outputDir, 'out.json'), + ), groups: [], slug: 'command-object-plugin', title: 'command-object plugin', diff --git a/packages/models/test/fixtures/code-pushup.config.mock.ts b/packages/models/test/fixtures/code-pushup.config.mock.ts index cce43a077..87d21092e 100644 --- a/packages/models/test/fixtures/code-pushup.config.mock.ts +++ b/packages/models/test/fixtures/code-pushup.config.mock.ts @@ -1,6 +1,7 @@ +import { join } from 'path'; import { CoreConfig } from '../../src'; +import { echoRunnerConfig } from './echo-runner-config.mock'; import { auditReport } from './plugin-config.mock'; -import { runnerConfig } from './runner.mock'; const outputDir = 'tmp'; export default { @@ -21,7 +22,7 @@ export default { docsUrl: 'http://www.my-docs.dev', }, ], - runner: runnerConfig([auditReport()], `${outputDir}/out.json`), + runner: echoRunnerConfig([auditReport()], join(outputDir, 'out.json')), groups: [], slug: 'command-object-plugin', title: 'command-object plugin', diff --git a/packages/models/test/fixtures/echo-runner-config.mock.ts b/packages/models/test/fixtures/echo-runner-config.mock.ts new file mode 100644 index 000000000..26796a01b --- /dev/null +++ b/packages/models/test/fixtures/echo-runner-config.mock.ts @@ -0,0 +1,17 @@ +import { platform } from 'os'; +import { AuditOutput, RunnerConfig } from '../../src'; + +export function echoRunnerConfig( + output: AuditOutput[], + outputFile: string, +): RunnerConfig { + const auditOutput = + platform() === 'win32' + ? JSON.stringify(output) + : "'" + JSON.stringify(output) + "'"; + return { + command: 'echo', + args: [auditOutput, '>', outputFile], + outputFile, + }; +} diff --git a/packages/models/test/fixtures/eslint-plugin.mock.ts b/packages/models/test/fixtures/eslint-plugin.mock.ts index 1b542d017..649f2e2af 100644 --- a/packages/models/test/fixtures/eslint-plugin.mock.ts +++ b/packages/models/test/fixtures/eslint-plugin.mock.ts @@ -5,8 +5,8 @@ import type { PluginConfig, PluginReport, } from '../../src'; +import { echoRunnerConfig } from './echo-runner-config.mock'; import { ESLINT_AUDITS_MAP } from './eslint-audits.mock'; -import { runnerConfig } from './runner.mock'; const eslintMeta = { slug: 'eslint', @@ -29,7 +29,7 @@ export function eslintPluginConfig(outputDir = 'tmp'): PluginConfig { ); return { ...eslintMeta, - runner: runnerConfig( + runner: echoRunnerConfig( Object.values(ESLINT_AUDITS_MAP), join(outputDir, 'eslint-out.json'), ), diff --git a/packages/models/test/fixtures/lighthouse-plugin.mock.ts b/packages/models/test/fixtures/lighthouse-plugin.mock.ts index 27b66c75e..0b3e8fb53 100644 --- a/packages/models/test/fixtures/lighthouse-plugin.mock.ts +++ b/packages/models/test/fixtures/lighthouse-plugin.mock.ts @@ -1,7 +1,8 @@ +import { join } from 'path'; import type { AuditGroup, PluginReport } from '../../src'; import { Audit, PluginConfig } from '../../src'; +import { echoRunnerConfig } from './echo-runner-config.mock'; import { LIGHTHOUSE_AUDIT_REPORTS_MAP } from './lighthouse-audits.mock'; -import { runnerConfig } from './runner.mock'; const PLUGIN_GROUP_PERFORMANCE: AuditGroup = { slug: 'performance', @@ -50,9 +51,9 @@ export function lighthousePluginConfig(outputDir = 'tmp'): PluginConfig { ); return { ...lighthousePluginMeta, - runner: runnerConfig( + runner: echoRunnerConfig( Object.values(LIGHTHOUSE_AUDIT_REPORTS_MAP), - `${outputDir}/lighthouse-out.json`, + join(outputDir, 'lighthouse-out.json'), ), audits, groups: [PLUGIN_GROUP_PERFORMANCE], diff --git a/packages/models/test/fixtures/plugin-config.mock.ts b/packages/models/test/fixtures/plugin-config.mock.ts index 9cdb47b36..c441db97b 100644 --- a/packages/models/test/fixtures/plugin-config.mock.ts +++ b/packages/models/test/fixtures/plugin-config.mock.ts @@ -1,6 +1,6 @@ import { join } from 'node:path'; import { Audit, AuditReport, PluginConfig } from '../../src'; -import { runnerConfig } from './runner.mock'; +import { echoRunnerConfig } from './echo-runner-config.mock'; export function pluginConfig( auditOutputs: AuditReport[], @@ -15,7 +15,7 @@ export function pluginConfig( description: 'Plugin description', docsUrl: 'https://my-plugin.docs.dev?1', audits: auditOutputs.map(auditOutput => auditConfig(auditOutput)), - runner: runnerConfig(auditOutputs, pluginOutputPath), + runner: echoRunnerConfig(auditOutputs, pluginOutputPath), ...(opt || {}), } satisfies PluginConfig; } diff --git a/packages/models/test/fixtures/runner.mock.ts b/packages/models/test/fixtures/runner.mock.ts deleted file mode 100644 index 7bc8f0901..000000000 --- a/packages/models/test/fixtures/runner.mock.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { AuditReport, RunnerConfig } from '../../src'; - -export function runnerConfig( - audits: AuditReport[], - outputFile = 'tmp/out.json', -): RunnerConfig { - return { - command: 'node', - args: [ - '-e', - `require('fs').writeFileSync('${outputFile}', '${JSON.stringify( - audits, - )}')`, - ], - outputFile, - }; -} diff --git a/packages/models/test/index.ts b/packages/models/test/index.ts index 8adf1fddd..61055c30c 100644 --- a/packages/models/test/index.ts +++ b/packages/models/test/index.ts @@ -1,15 +1,15 @@ -export * from './memfs'; -export { report } from './fixtures/report.mock'; export { config, minimalConfig, minimalReport } from './fixtures/config.mock'; +export { report } from './fixtures/report.mock'; +export * from './memfs'; export { eslintPluginConfig } from './fixtures/eslint-plugin.mock'; export { lighthousePluginConfig } from './fixtures/lighthouse-plugin.mock'; +export { echoRunnerConfig } from './fixtures/echo-runner-config.mock'; +export { persistConfig } from './fixtures/persist-config.mock'; export { - pluginConfig, auditConfig, auditReport, + pluginConfig, } from './fixtures/plugin-config.mock'; -export { runnerConfig } from './fixtures/runner.mock'; -export { persistConfig } from './fixtures/persist-config.mock'; export { uploadConfig } from './fixtures/upload-config.mock'; diff --git a/packages/models/test/schema.mock.ts b/packages/models/test/schema.mock.ts index 47d35f9d1..cd472ab73 100644 --- a/packages/models/test/schema.mock.ts +++ b/packages/models/test/schema.mock.ts @@ -1,3 +1,4 @@ +import { join } from 'path'; import { Audit, AuditGroup, Issue, PersistConfig } from '../src/'; const __auditSlug__ = 'mock-audit-slug'; @@ -19,7 +20,7 @@ export function mockAuditConfig(opt?: { auditSlug?: string }): Audit { export function mockPersistConfig(opt?: Partial): PersistConfig { let { outputDir, format } = opt || {}; - outputDir = outputDir || `tmp/${__outputFile__}`; + outputDir = outputDir || join('tmp', __outputFile__); format = format || []; return { outputDir, diff --git a/packages/plugin-eslint/src/lib/__snapshots__/eslint-plugin.spec.ts.snap b/packages/plugin-eslint/src/lib/__snapshots__/eslint-plugin.spec.ts.snap index c2e175eec..3af5a1ada 100644 --- a/packages/plugin-eslint/src/lib/__snapshots__/eslint-plugin.spec.ts.snap +++ b/packages/plugin-eslint/src/lib/__snapshots__/eslint-plugin.spec.ts.snap @@ -443,8 +443,8 @@ Custom options: "/bin.js", "typescript-eslint-adjacent-overload-signatures,typescript-eslint-ban-ts-comment,typescript-eslint-ban-types,typescript-eslint-no-array-constructor,typescript-eslint-no-empty-function,typescript-eslint-no-empty-interface,typescript-eslint-no-explicit-any,typescript-eslint-no-extra-non-null-assertion,typescript-eslint-no-extra-semi,typescript-eslint-no-inferrable-types,typescript-eslint-no-loss-of-precision,typescript-eslint-no-misused-new,typescript-eslint-no-namespace,typescript-eslint-no-non-null-asserted-optional-chain,typescript-eslint-no-non-null-assertion,typescript-eslint-no-this-alias,typescript-eslint-no-unnecessary-type-constraint,typescript-eslint-no-unused-vars,typescript-eslint-no-var-requires,typescript-eslint-prefer-as-const,typescript-eslint-prefer-namespace-keyword,typescript-eslint-triple-slash-reference,no-var,prefer-const,prefer-rest-params,prefer-spread,for-direction,no-async-promise-executor,no-case-declarations,no-class-assign,no-compare-neg-zero,no-cond-assign,no-constant-condition,no-control-regex,no-debugger,no-delete-var,no-dupe-else-if,no-duplicate-case,no-empty,no-empty-character-class,no-empty-pattern,no-ex-assign,no-extra-boolean-cast,no-fallthrough,no-global-assign,no-inner-declarations,no-invalid-regexp,no-irregular-whitespace,no-misleading-character-class,no-mixed-spaces-and-tabs,no-nonoctal-decimal-escape,no-octal,no-prototype-builtins,no-regex-spaces,no-self-assign,no-shadow-restricted-names,no-sparse-arrays,no-unexpected-multiline,no-unsafe-finally,no-unsafe-optional-chaining,no-unused-labels,no-useless-backreference,no-useless-catch,no-useless-escape,no-with,require-yield,use-isnan,nx-enforce-module-boundaries-9dba9763586d15c6,nx-dependency-checks", "./packages/utils/.eslintrc.json", - "packages/utils/**/*.ts", - "packages/utils/**/*.json", + "'packages/utils/**/*.ts'", + "'packages/utils/**/*.json'", ], "command": "node", "outputFile": "node_modules/.code-pushup/eslint/runner-output.json", @@ -749,8 +749,8 @@ exports[`eslintPlugin > should initialize ESLint plugin for React application 1` "/bin.js", "no-cond-assign,no-const-assign,no-debugger,no-invalid-regexp,no-undef,no-unreachable-loop,no-unsafe-negation,no-unsafe-optional-chaining,no-unused-vars,use-isnan,valid-typeof,arrow-body-style,camelcase,curly,eqeqeq,max-lines-per-function,max-lines,no-shadow,no-var,object-shorthand,prefer-arrow-callback,prefer-const,prefer-object-spread,yoda,react-jsx-key,react-prop-types,react-react-in-jsx-scope,react-hooks-rules-of-hooks,react-hooks-exhaustive-deps,react-display-name,react-jsx-no-comment-textnodes,react-jsx-no-duplicate-props,react-jsx-no-target-blank,react-jsx-no-undef,react-jsx-uses-react,react-jsx-uses-vars,react-no-children-prop,react-no-danger-with-children,react-no-deprecated,react-no-direct-mutation-state,react-no-find-dom-node,react-no-is-mounted,react-no-render-return-value,react-no-string-refs,react-no-unescaped-entities,react-no-unknown-property,react-require-render-return", ".eslintrc.js", - "src/**/*.js", - "src/**/*.jsx", + "'src/**/*.js'", + "'src/**/*.jsx'", ], "command": "node", "outputFile": "node_modules/.code-pushup/eslint/runner-output.json", diff --git a/packages/plugin-eslint/src/lib/eslint-plugin.spec.ts b/packages/plugin-eslint/src/lib/eslint-plugin.spec.ts index 29ce64376..eec1f6d14 100644 --- a/packages/plugin-eslint/src/lib/eslint-plugin.spec.ts +++ b/packages/plugin-eslint/src/lib/eslint-plugin.spec.ts @@ -1,7 +1,9 @@ +import os from 'os'; import { dirname, join } from 'path'; import { fileURLToPath } from 'url'; import type { SpyInstance } from 'vitest'; import { PluginConfig } from '@code-pushup/models'; +import { toUnixPath } from '@code-pushup/utils'; import { eslintPlugin } from './eslint-plugin'; describe('eslintPlugin', () => { @@ -14,23 +16,29 @@ describe('eslintPlugin', () => { ); let cwdSpy: SpyInstance; + let platformSpy: SpyInstance; const replaceAbsolutePath = (plugin: PluginConfig): PluginConfig => ({ ...plugin, runner: { ...plugin.runner, args: plugin.runner.args?.map(arg => - arg.replace(fileURLToPath(dirname(import.meta.url)), ''), + toUnixPath( + arg.replace(fileURLToPath(dirname(import.meta.url)), ''), + ), ), }, }); beforeAll(() => { cwdSpy = vi.spyOn(process, 'cwd'); + // Linux produces extra quotation marks for globs + platformSpy = vi.spyOn(os, 'platform').mockReturnValue('linux'); }); afterAll(() => { cwdSpy.mockRestore(); + platformSpy.mockRestore(); }); it('should initialize ESLint plugin for React application', async () => { diff --git a/packages/plugin-eslint/src/lib/runner.spec.ts b/packages/plugin-eslint/src/lib/runner.spec.ts index 1efedf160..64837f75c 100644 --- a/packages/plugin-eslint/src/lib/runner.spec.ts +++ b/packages/plugin-eslint/src/lib/runner.spec.ts @@ -1,4 +1,5 @@ import { ESLint } from 'eslint'; +import os from 'os'; import { dirname, join } from 'path'; import { fileURLToPath } from 'url'; import type { SpyInstance } from 'vitest'; @@ -12,6 +13,7 @@ import { describe('executeRunner', () => { let cwdSpy: SpyInstance; + let platformSpy: SpyInstance; let argv: string[]; beforeAll(async () => { @@ -24,6 +26,8 @@ describe('executeRunner', () => { 'todos-app', ); cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue(appDir); + // Windows does not require additional quotation marks for globs + platformSpy = vi.spyOn(os, 'platform').mockReturnValue('win32'); const eslintrc = '.eslintrc.js'; const patterns = ['src/**/*.js', 'src/**/*.jsx']; @@ -45,6 +49,7 @@ describe('executeRunner', () => { afterAll(() => { cwdSpy.mockRestore(); + platformSpy.mockRestore(); }); it('should execute ESLint and create audit results for React application', async () => { diff --git a/packages/plugin-eslint/src/lib/runner/index.ts b/packages/plugin-eslint/src/lib/runner/index.ts index 3d52b1f0d..5a803346e 100644 --- a/packages/plugin-eslint/src/lib/runner/index.ts +++ b/packages/plugin-eslint/src/lib/runner/index.ts @@ -1,4 +1,5 @@ import { mkdir, writeFile } from 'fs/promises'; +import { platform } from 'os'; import { dirname } from 'path'; import type { Audit, AuditOutput, RunnerConfig } from '@code-pushup/models'; import { toArray } from '@code-pushup/utils'; @@ -51,7 +52,9 @@ export function createRunnerConfig( scriptPath, audits.map(audit => audit.slug).join(AUDIT_SLUGS_SEP), eslintrc, - ...toArray(patterns), + ...toArray(patterns).map(pattern => + platform() === 'win32' ? pattern : `'${pattern}'`, + ), ], outputFile: RUNNER_OUTPUT_PATH, }; diff --git a/packages/plugin-eslint/src/lib/runner/lint.ts b/packages/plugin-eslint/src/lib/runner/lint.ts index 8762875d6..2638dc176 100644 --- a/packages/plugin-eslint/src/lib/runner/lint.ts +++ b/packages/plugin-eslint/src/lib/runner/lint.ts @@ -1,6 +1,5 @@ import { ESLint, type Linter } from 'eslint'; -import { sep } from 'path'; -import { distinct, toArray } from '@code-pushup/utils'; +import { distinct, toArray, toUnixPath } from '@code-pushup/utils'; import type { LintResult, LinterOutput, RuleOptionsPerFile } from './types'; export async function lint( @@ -16,7 +15,7 @@ export async function lint( const results = lintResults.map( (result): LintResult => ({ ...result, - relativeFilePath: result.filePath.replace(process.cwd() + sep, ''), + relativeFilePath: toUnixPath(result.filePath, { toRelative: true }), }), ); diff --git a/packages/plugin-lighthouse/src/lib/lighthouse-plugin.ts b/packages/plugin-lighthouse/src/lib/lighthouse-plugin.ts index 1ab5c4239..3073e0691 100644 --- a/packages/plugin-lighthouse/src/lib/lighthouse-plugin.ts +++ b/packages/plugin-lighthouse/src/lib/lighthouse-plugin.ts @@ -1,5 +1,7 @@ import { defaultConfig } from 'lighthouse'; -import { AuditOutputs, PluginConfig } from '@code-pushup/models'; +import { join } from 'path'; +import { PluginConfig } from '@code-pushup/models'; +import { echoRunnerConfig } from '@code-pushup/models/testing'; type LighthousePluginConfig = { config: string; @@ -16,20 +18,16 @@ export function lighthousePlugin(_: LighthousePluginConfig): PluginConfig { title: 'Largest Contentful Paint', }, ], - runner: { - command: 'node', - args: [ - '-e', - `require('fs').writeFileSync('tmp/out.json', '${JSON.stringify([ - { - slug: 'largest-contentful-paint', - value: 0, - score: 0, - }, - ] satisfies AuditOutputs)}')`, + runner: echoRunnerConfig( + [ + { + slug: 'largest-contentful-paint', + value: 0, + score: 0, + }, ], - outputFile: 'tmp/out.json', - }, + join('tmp', 'out.json'), + ), slug: 'lighthouse', title: 'ChromeDevTools Lighthouse', icon: 'lighthouse', diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index b8da33863..f7beb4c8e 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,5 +1,3 @@ -export { ScoredReport, scoreReport } from './lib/scoring'; -export { verboseUtils } from './lib/verbose-utils'; export { CliArgsObject, ProcessConfig, @@ -11,9 +9,18 @@ export { } from './lib/execute-process'; export { git, latestHash } from './lib/git'; export { importEsmModule } from './lib/load-file'; -export { FOOTER_PREFIX, CODE_PUSHUP_DOMAIN } from './lib/report'; +export { + CODE_PUSHUP_DOMAIN, + FOOTER_PREFIX, + calcDuration, + compareIssueSeverity, + formatBytes, + formatCount, + slugify, +} from './lib/report'; export { reportToMd } from './lib/report-to-md'; export { reportToStdout } from './lib/report-to-stdout'; +export { ScoredReport, scoreReport } from './lib/scoring'; export { countOccurrences, distinct, @@ -22,11 +29,6 @@ export { readJsonFile, readTextFile, toArray, + toUnixPath, } from './lib/utils'; -export { - formatBytes, - slugify, - calcDuration, - formatCount, - compareIssueSeverity, -} from './lib/report'; +export { verboseUtils } from './lib/verbose-utils'; diff --git a/packages/utils/src/lib/execute-process.ts b/packages/utils/src/lib/execute-process.ts index 641876cfe..1d7290900 100644 --- a/packages/utils/src/lib/execute-process.ts +++ b/packages/utils/src/lib/execute-process.ts @@ -137,7 +137,7 @@ export function executeProcess(cfg: ProcessConfig): Promise { const date = new Date().toISOString(); const start = performance.now(); return new Promise((resolve, reject) => { - const process = spawn(cfg.command, cfg.args, { cwd }); + const process = spawn(cfg.command, cfg.args, { cwd, shell: true }); let stdout = ''; let stderr = ''; diff --git a/packages/utils/src/lib/utils.spec.ts b/packages/utils/src/lib/utils.spec.ts index 653a34059..527edc52d 100644 --- a/packages/utils/src/lib/utils.spec.ts +++ b/packages/utils/src/lib/utils.spec.ts @@ -1,5 +1,11 @@ import { describe, expect } from 'vitest'; -import { countOccurrences, distinct, pluralize, toArray } from './utils'; +import { + countOccurrences, + distinct, + pluralize, + toArray, + toUnixPath, +} from './utils'; describe('pluralize', () => { it.each([ @@ -30,6 +36,28 @@ describe('countOccurrences', () => { }); }); +describe('toUnixPath', () => { + it.each([ + ['main.ts', 'main.ts'], + ['src/main.ts', 'src/main.ts'], + ['../../relative/unix/path/index.ts', '../../relative/unix/path/index.ts'], + [ + '..\\..\\relative\\windows\\path\\index.ts', + '../../relative/windows/path/index.ts', + ], + ])('should transform "%s" to valid slug "%s"', (path, unixPath) => { + expect(toUnixPath(path)).toBe(unixPath); + }); + + it('should transform absolute Windows path to relative UNIX path', () => { + expect( + toUnixPath(`${process.cwd()}\\windows\\path\\config.ts`, { + toRelative: true, + }), + ).toBe('windows/path/config.ts'); + }); +}); + describe('distinct', () => { it('should remove duplicate strings from array', () => { expect( diff --git a/packages/utils/src/lib/utils.ts b/packages/utils/src/lib/utils.ts index 399e4ed71..f0a965afe 100644 --- a/packages/utils/src/lib/utils.ts +++ b/packages/utils/src/lib/utils.ts @@ -32,6 +32,19 @@ export function countOccurrences( ); } +export function toUnixPath( + path: string, + options?: { toRelative?: boolean }, +): string { + const unixPath = path.replace(/\\/g, '/'); + + if (options?.toRelative) { + return unixPath.replace(process.cwd().replace(/\\/g, '/') + '/', ''); + } + + return unixPath; +} + // === Validation export function distinct(array: T[]): T[] { diff --git a/tsconfig.base.json b/tsconfig.base.json index 10aeb7dcd..0c440133e 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -17,6 +17,7 @@ "skipDefaultLibCheck": true, "baseUrl": ".", "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, "paths": { "@code-pushup/cli": ["packages/cli/src/index.ts"], "@code-pushup/cli/testing": ["packages/cli/test/index.ts"],