diff --git a/lint-staged.config.js b/lint-staged.config.js index 39d0b6f62b71a..0a64c208cbe57 100644 --- a/lint-staged.config.js +++ b/lint-staged.config.js @@ -13,12 +13,14 @@ const commands = { lint: 'node ./scripts/lint-staged/src/eslint', }; +const nonJsExtensions = [ + prettierSupportedFileExtensionsByContext.stylesheets, + prettierSupportedFileExtensionsByContext.markdown, + prettierSupportedFileExtensionsByContext.others, +].flat(); + // https://www.npmjs.com/package/lint-staged module.exports = { - [`**/*.{${[].concat( - prettierSupportedFileExtensionsByContext.stylesheets, - prettierSupportedFileExtensionsByContext.markdown, - prettierSupportedFileExtensionsByContext.others, - )}}`]: [commands.format], - [`**/*.{${prettierSupportedFileExtensionsByContext.js}}`]: [commands.format, commands.lint], + [`**/*.{${nonJsExtensions}}`]: [commands.format], + [`**/*.{${prettierSupportedFileExtensionsByContext.js}}`]: [/* commands.format, */ commands.lint], }; diff --git a/scripts/lint-staged/package.json b/scripts/lint-staged/package.json index 6857f42407935..780b52b673daa 100644 --- a/scripts/lint-staged/package.json +++ b/scripts/lint-staged/package.json @@ -11,7 +11,8 @@ "type-check": "just-scripts type-check" }, "dependencies": { - "@fluentui/scripts-monorepo": "*" + "@fluentui/scripts-monorepo": "*", + "@fluentui/scripts-utils": "*" }, "devDependencies": {} } diff --git a/scripts/lint-staged/src/eslint-for-package.js b/scripts/lint-staged/src/eslint-for-package.js index e992ab9d9669d..831dd98346fac 100644 --- a/scripts/lint-staged/src/eslint-for-package.js +++ b/scripts/lint-staged/src/eslint-for-package.js @@ -1,10 +1,10 @@ // @ts-check +const fs = require('fs'); const path = require('path'); const { eslintConstants } = require('@fluentui/scripts-monorepo'); const { ESLint } = require('eslint'); -const fs = require('fs-extra'); const micromatch = require('micromatch'); /** @@ -21,8 +21,9 @@ const micromatch = require('micromatch'); async function run() { // Get information needed to determine whether files in this package should be linted const packagePath = process.cwd(); - const packageJson = fs.readJSONSync(path.join(packagePath, 'package.json')); - const lintScript = packageJson.scripts.lint || ''; + /** @type {{scripts?:Record}} */ + const packageJson = JSON.parse(fs.readFileSync(path.join(packagePath, 'package.json'), 'utf-8')); + const lintScript = packageJson.scripts?.lint ?? ''; /** @type {import('eslint').ESLint} */ let eslint; /** @type {string} */ @@ -43,7 +44,7 @@ async function run() { eslint = new ESLint({ fix: true, cache: lintScript.includes('--cache') }); } - // files are provided by @see `./eslint.js` via cli + // files are provided by @see `file://./eslint.js` via cli const cliFiles = process.argv.slice(2); // Filter out files with non-linted extensions const files = cliFiles.filter(file => micromatch.isMatch(file, includePattern)); diff --git a/scripts/lint-staged/src/eslint.js b/scripts/lint-staged/src/eslint.js index d127501da4e19..69f13146ef7e2 100644 --- a/scripts/lint-staged/src/eslint.js +++ b/scripts/lint-staged/src/eslint.js @@ -7,7 +7,7 @@ const os = require('os'); const path = require('path'); const { promisify } = require('util'); -const { getWorkspaceProjects, workspaceRoot } = require('@fluentui/scripts-monorepo'); +const { findConfig } = require('@fluentui/scripts-utils'); const { default: PQueue } = require('p-queue'); const exec = promisify(child_process.exec); @@ -16,44 +16,37 @@ const exec = promisify(child_process.exec); */ const eslintForPackageScript = path.join(__dirname, 'eslint-for-package.js'); -const files = process.argv.slice(2); - /** * Since we have an eslint config file per package we need to respect this when running * eslint for staged files. To do this we group the files per package name. This function takes * a list of package names and returns an object with the package root as the key and the files * in that package as the value. + * + * @param {string[]} files * @returns {{ [packagePath: string]: string[] }} */ -function groupFilesByPackage() { +function groupFilesByPackage(files) { /** @type {{ [packagePath: string]: string[] }} */ const filesByPackage = {}; - const packagesWithEslint = []; - const projects = getWorkspaceProjects(); - for (const [, projectConfig] of projects) { - const absoluteRootPath = path.join(workspaceRoot, projectConfig.root); - if (fs.readdirSync(absoluteRootPath).some(f => f.startsWith('.eslintrc'))) { - packagesWithEslint.push(absoluteRootPath); + for (const file of files) { + const packagePath = findConfig('project.json', file)?.replace('/project.json', ''); + + if (!packagePath) { + continue; } - } - for (const file of files) { - // eslint-disable-next-line no-shadow - const packagePath = packagesWithEslint.find(packagePath => { - // if file lives within searched package we will get only shortened absolute path `/src/abc.ts` - // we add `.` to make it relative and thus have match pattern to check upon - const normalizedFilePath = file.replace(packagePath, '.'); - return normalizedFilePath.startsWith('./'); - }); + const hasEslintConfig = + fs.existsSync(path.join(packagePath, '.eslintrc.json')) || fs.existsSync(path.join(packagePath, '.eslintrc.js')); - // Exclude files in a package without an eslintrc (or not in a package at all) - if (packagePath) { - if (!filesByPackage[packagePath]) { - filesByPackage[packagePath] = []; - } - filesByPackage[packagePath].push(file); + if (!hasEslintConfig) { + continue; } + + if (!filesByPackage[packagePath]) { + filesByPackage[packagePath] = []; + } + filesByPackage[packagePath].push(file); } return filesByPackage; @@ -61,24 +54,26 @@ function groupFilesByPackage() { /** * Runs eslint for the staged files in the packages that require it. + * @param {string[]} files */ -async function runEslintOnFilesGroupedPerPackage() { - const filesGroupedByPackage = groupFilesByPackage(); +async function runEslintOnFilesGroupedPerPackage(files) { + const filesGroupedByPackage = groupFilesByPackage(files); // Log an empty line on error to make the eslint output look better - console.log(''); + console.log('', filesGroupedByPackage); const queue = new PQueue({ concurrency: os.cpus().length / 2 }); let hasError = false; await queue.addAll( // eslint-disable-next-line no-shadow - Object.entries(filesGroupedByPackage).map(([packagePath, files]) => async () => { + Object.entries(filesGroupedByPackage).map(([packageRootAbsolutePath, files]) => async () => { // This script handles running eslint on ONLY touched files for each package. const cmd = `node ${eslintForPackageScript} ${files.join(' ')}`; + console.log(`${cmd}`); return ( - exec(cmd, { cwd: packagePath }) + exec(cmd, { cwd: packageRootAbsolutePath }) // Log severity:error lint reports including severity:warn // this will also result in killing the process .catch((/** @type {{ stdout: string, stderr: string }} */ err) => { @@ -106,7 +101,12 @@ async function runEslintOnFilesGroupedPerPackage() { } function main() { - runEslintOnFilesGroupedPerPackage().catch(err => { + /** + * Files that are staged for commit - Absolute paths + */ + const files = process.argv.slice(2); + + runEslintOnFilesGroupedPerPackage(files).catch(err => { console.error(err); process.exit(1); });