diff --git a/build-tests/install-test-workspace/build.js b/build-tests/install-test-workspace/build.js index 47a66aeb7b9..43bfa168771 100644 --- a/build-tests/install-test-workspace/build.js +++ b/build-tests/install-test-workspace/build.js @@ -139,7 +139,11 @@ const pnpmInstallArgs = [ '--recursive', '--link-workspace-packages=false', // PNPM gets confused by the rewriting performed by our .pnpmfile.cjs afterAllResolved hook - '--frozen-lockfile=false' + '--frozen-lockfile=false', + + // Workaround for this issue: + // https://github.com/pnpm/pnpm/issues/6778#issuecomment-1762418094 + '--config.confirmModulesPurge=false' ]; console.log('\nInstalling:'); @@ -148,7 +152,7 @@ console.log(' pnpm ' + pnpmInstallArgs.join(' ')); checkSpawnResult( Executable.spawnSync(rushConfiguration.packageManagerToolFilename, pnpmInstallArgs, { currentWorkingDirectory: path.join(__dirname, 'workspace'), - stdio: 'inherit' + stdio: ['ignore', 'inherit', 'inherit'] }), 'pnpm install' ); @@ -187,7 +191,7 @@ console.log('\nBuilding projects...\n'); checkSpawnResult( Executable.spawnSync(rushConfiguration.packageManagerToolFilename, ['run', '--recursive', 'build'], { currentWorkingDirectory: path.join(__dirname, 'workspace'), - stdio: 'inherit' + stdio: ['ignore', 'inherit', 'inherit'] }), 'pnpm run' ); diff --git a/build-tests/install-test-workspace/workspace/common/pnpm-lock.yaml b/build-tests/install-test-workspace/workspace/common/pnpm-lock.yaml index 3be1886311d..7bdabc351f0 100644 --- a/build-tests/install-test-workspace/workspace/common/pnpm-lock.yaml +++ b/build-tests/install-test-workspace/workspace/common/pnpm-lock.yaml @@ -11,8 +11,8 @@ importers: rush-lib-test: dependencies: '@microsoft/rush-lib': - specifier: file:microsoft-rush-lib-5.107.4.tgz - version: file:../temp/tarballs/microsoft-rush-lib-5.107.4.tgz(@types/node@18.17.15) + specifier: file:microsoft-rush-lib-5.109.1.tgz + version: file:../temp/tarballs/microsoft-rush-lib-5.109.1.tgz(@types/node@18.17.15) colors: specifier: ^1.4.0 version: 1.4.0 @@ -30,15 +30,15 @@ importers: rush-sdk-test: dependencies: '@rushstack/rush-sdk': - specifier: file:rushstack-rush-sdk-5.107.4.tgz - version: file:../temp/tarballs/rushstack-rush-sdk-5.107.4.tgz(@types/node@18.17.15) + specifier: file:rushstack-rush-sdk-5.109.1.tgz + version: file:../temp/tarballs/rushstack-rush-sdk-5.109.1.tgz(@types/node@18.17.15) colors: specifier: ^1.4.0 version: 1.4.0 devDependencies: '@microsoft/rush-lib': - specifier: file:microsoft-rush-lib-5.107.4.tgz - version: file:../temp/tarballs/microsoft-rush-lib-5.107.4.tgz(@types/node@18.17.15) + specifier: file:microsoft-rush-lib-5.109.1.tgz + version: file:../temp/tarballs/microsoft-rush-lib-5.109.1.tgz(@types/node@18.17.15) '@types/node': specifier: 18.17.15 version: 18.17.15 @@ -2301,7 +2301,7 @@ packages: hasBin: true /json-buffer@3.0.0: - resolution: {integrity: sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==} + resolution: {integrity: sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=} /json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} @@ -3923,11 +3923,11 @@ packages: optionalDependencies: commander: 2.20.3 - file:../temp/tarballs/microsoft-rush-lib-5.107.4.tgz(@types/node@18.17.15): - resolution: {tarball: file:../temp/tarballs/microsoft-rush-lib-5.107.4.tgz} - id: file:../temp/tarballs/microsoft-rush-lib-5.107.4.tgz + file:../temp/tarballs/microsoft-rush-lib-5.109.1.tgz(@types/node@18.17.15): + resolution: {tarball: file:../temp/tarballs/microsoft-rush-lib-5.109.1.tgz} + id: file:../temp/tarballs/microsoft-rush-lib-5.109.1.tgz name: '@microsoft/rush-lib' - version: 5.107.4 + version: 5.109.1 engines: {node: '>=5.6.0'} dependencies: '@pnpm/dependency-path': 2.1.2 @@ -4264,11 +4264,11 @@ packages: resolve: 1.22.1 strip-json-comments: 3.1.1 - file:../temp/tarballs/rushstack-rush-sdk-5.107.4.tgz(@types/node@18.17.15): - resolution: {tarball: file:../temp/tarballs/rushstack-rush-sdk-5.107.4.tgz} - id: file:../temp/tarballs/rushstack-rush-sdk-5.107.4.tgz + file:../temp/tarballs/rushstack-rush-sdk-5.109.1.tgz(@types/node@18.17.15): + resolution: {tarball: file:../temp/tarballs/rushstack-rush-sdk-5.109.1.tgz} + id: file:../temp/tarballs/rushstack-rush-sdk-5.109.1.tgz name: '@rushstack/rush-sdk' - version: 5.107.4 + version: 5.109.1 dependencies: '@rushstack/node-core-library': file:../temp/tarballs/rushstack-node-core-library-3.61.0.tgz(@types/node@18.17.15) '@types/node-fetch': 2.6.2 diff --git a/common/config/rush/browser-approved-packages.json b/common/config/rush/browser-approved-packages.json index 3d81a225bff..12066a1ece3 100644 --- a/common/config/rush/browser-approved-packages.json +++ b/common/config/rush/browser-approved-packages.json @@ -46,21 +46,9 @@ "name": "@rushstack/rush-vscode-command-webview", "allowedCategories": [ "vscode-extensions" ] }, - { - "name": "@typescript-eslint/scope-manager", - "allowedCategories": [ "libraries", "tests" ] - }, - { - "name": "@typescript-eslint/type-utils", - "allowedCategories": [ "libraries" ] - }, { "name": "@typescript-eslint/types", - "allowedCategories": [ "libraries", "tests" ] - }, - { - "name": "@typescript-eslint/utils", - "allowedCategories": [ "libraries", "tests" ] + "allowedCategories": [ "libraries" ] }, { "name": "@vscode/test-electron", diff --git a/common/config/rush/nonbrowser-approved-packages.json b/common/config/rush/nonbrowser-approved-packages.json index aebe60b531e..52f780f33fe 100644 --- a/common/config/rush/nonbrowser-approved-packages.json +++ b/common/config/rush/nonbrowser-approved-packages.json @@ -104,7 +104,7 @@ }, { "name": "@rushstack/eslint-plugin", - "allowedCategories": [ "libraries", "tests" ] + "allowedCategories": [ "libraries" ] }, { "name": "@rushstack/eslint-plugin-packlets", @@ -328,7 +328,7 @@ }, { "name": "@typescript-eslint/eslint-plugin", - "allowedCategories": [ "libraries", "tests", "vscode-extensions" ] + "allowedCategories": [ "libraries", "vscode-extensions" ] }, { "name": "@typescript-eslint/experimental-utils", @@ -340,7 +340,7 @@ }, { "name": "@typescript-eslint/typescript-estree", - "allowedCategories": [ "libraries", "tests" ] + "allowedCategories": [ "libraries" ] }, { "name": "@yarnpkg/lockfile", diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index f6089006524..dca459d2b54 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -1102,18 +1102,6 @@ importers: specifier: ~5.0.4 version: 5.0.4 - ../../build-tests/eslint-bulk-suppressions-test: - devDependencies: - '@rushstack/heft': - specifier: workspace:* - version: link:../../apps/heft - eslint: - specifier: ~8.7.0 - version: 8.7.0 - local-node-rig: - specifier: workspace:* - version: link:../../rigs/local-node-rig - ../../build-tests/hashed-folder-copy-plugin-webpack5-test: devDependencies: '@rushstack/hashed-folder-copy-plugin': @@ -1710,10 +1698,10 @@ importers: version: link:../../heft-plugins/heft-typescript-plugin '@types/jest': specifier: ts4.9 - version: 29.5.6 + version: 29.5.7 '@types/node': specifier: ts4.9 - version: 20.8.9 + version: 20.8.10 eslint: specifier: ~8.7.0 version: 8.7.0 @@ -2232,12 +2220,6 @@ importers: '@types/node': specifier: 18.17.15 version: 18.17.15 - '@typescript-eslint/types': - specifier: ~6.5.0 - version: 6.5.0(typescript@5.0.4) - typescript: - specifier: ~5.0.4 - version: 5.0.4 ../../eslint/eslint-plugin: dependencies: @@ -11259,8 +11241,8 @@ packages: expect: 29.6.2 pretty-format: 29.6.2 - /@types/jest@29.5.6: - resolution: {integrity: sha512-/t9NnzkOpXb4Nfvg17ieHE6EeSjDS2SGSpNYfoLbUAeL/EOueU/RSdOWFpfQTXBEM7BguYW1XQ0EbM+6RlIh6w==} + /@types/jest@29.5.7: + resolution: {integrity: sha512-HLyetab6KVPSiF+7pFcUyMeLsx25LDNDemw9mGsJBkai/oouwrjTycocSDYopMEwFhN2Y4s9oPyOCZNofgSt2g==} dependencies: expect: 29.6.2 pretty-format: 29.6.2 @@ -11372,8 +11354,8 @@ packages: /@types/node@18.17.15: resolution: {integrity: sha512-2yrWpBk32tvV/JAd3HNHWuZn/VDN1P+72hWirHnvsvTGSqbANi+kSeuQR9yAHnbvaBvHDsoTdXV0Fe+iRtHLKA==} - /@types/node@20.8.9: - resolution: {integrity: sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==} + /@types/node@20.8.10: + resolution: {integrity: sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==} dependencies: undici-types: 5.26.5 dev: true @@ -11782,15 +11764,6 @@ packages: dependencies: typescript: 5.0.4 - /@typescript-eslint/types@6.5.0(typescript@5.0.4): - resolution: {integrity: sha512-eqLLOEF5/lU8jW3Bw+8auf4lZSbbljHR2saKnYqON12G/WsJrGeeDHWuQePoEf9ro22+JkbPfWQwKEC5WwLQ3w==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - typescript: '*' - dependencies: - typescript: 5.0.4 - dev: true - /@typescript-eslint/types@6.7.3(typescript@5.0.4): resolution: {integrity: sha512-4g+de6roB2NFcfkZb439tigpAMnvEIg3rIjWQ+EM7IBaYt/CdJt6em9BJ4h4UpdgaBWdmx2iWsafHTrqmgIPNw==} engines: {node: ^16.0.0 || >=18.0.0} diff --git a/common/config/rush/repo-state.json b/common/config/rush/repo-state.json index bb59435fcc5..6a6b8adfa2d 100644 --- a/common/config/rush/repo-state.json +++ b/common/config/rush/repo-state.json @@ -1,5 +1,5 @@ // DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush. { - "pnpmShrinkwrapHash": "45738a4949849bf0e9cb40587762493c79568b06", + "pnpmShrinkwrapHash": "ff2c363cb9df388ef4607cffdd76a8987dcdfbed", "preferredVersionsHash": "1926a5b12ac8f4ab41e76503a0d1d0dccc9c0e06" } diff --git a/eslint/eslint-bulk/package.json b/eslint/eslint-bulk/package.json index c4491ba5d4b..8a76e6269f8 100755 --- a/eslint/eslint-bulk/package.json +++ b/eslint/eslint-bulk/package.json @@ -1,6 +1,6 @@ { "name": "@rushstack/eslint-bulk", - "version": "1.5.1", + "version": "0.0.0", "description": "A set of helper CLIs to use with the rushstack ESLint toolchain", "main": "lib/index.js", "license": "MIT", diff --git a/eslint/eslint-bulk/src/clean.ts b/eslint/eslint-bulk/src/clean.ts new file mode 100644 index 00000000000..937eb8ff8e9 --- /dev/null +++ b/eslint/eslint-bulk/src/clean.ts @@ -0,0 +1,50 @@ +#!/usr/bin/env node +import type { ExecException } from 'child_process'; +import { exec } from 'child_process'; +import { Command } from 'commander'; +import { whichEslint } from './utils/which-eslint'; +import { findPatch } from './utils/find-patch'; + +export function makeCleanCommand(): Command { + const clean = new Command('clean'); + clean + .description( + 'Delete unused suppression entries for the given file in the corresponding .eslint-bulk-suppressions.json file.' + ) + .argument( + '', + 'The "files" glob pattern argument follows the same rules as the "eslint" command.' + ) + .action((files: string[]) => { + console.log(findPatch()); + // const eslintCLI = whichEslint(); + + // const env = Object.assign({}, process.env); + // Object.assign(env, { ESLINT_BULK_CLEAN: 'true' }); + + // exec( + // `${eslintCLI} ${files.join(' ')}`, + // { env }, + // (error: ExecException | null, stdout: string, stderr: string) => { + // // if errorCount != 0, ESLint will process.exit(1) giving the false impression + // // that the exec failed, even though linting errors are to be expected + // const eslintOutputWithErrorRegex = /"errorCount":(?!0)\d+/; + // const isEslintError = error !== null && error.code === 1 && eslintOutputWithErrorRegex.test(stdout); + + // if (error && !isEslintError) { + // console.error(`Execution error: ${error.message}`); + // process.exit(1); + // } + + // if (stderr) { + // console.error(`ESLint errors: ${stderr}`); + // process.exit(1); + // } + + // console.log('Successfully cleaned up bulk suppressions'); + // } + // ); + }); + + return clean; +} diff --git a/eslint/eslint-bulk/src/suppress-commander.ts b/eslint/eslint-bulk/src/suppress-commander.ts new file mode 100644 index 00000000000..8a5e7d5777e --- /dev/null +++ b/eslint/eslint-bulk/src/suppress-commander.ts @@ -0,0 +1,63 @@ +#!/usr/bin/env node +import type { ExecException } from 'child_process'; +import { exec } from 'child_process'; +import { Command } from 'commander'; +import { whichEslint } from './utils/which-eslint'; + +export function makeSuppressCommand(): Command { + const suppress = new Command('suppress'); + suppress + .description( + 'Generate a new .eslint-bulk-suppressions.json file or add suppression entries to an existing file.' + ) + .argument( + '', + 'The "files" glob pattern argument follows the same rules as the "eslint" command.' + ) + .option('-R, --rule ', 'The full name of the ESlint rules you want to suppress.') + .option('-A, --all', 'Suppress all rules instead of specific rule(s).') + .action((files: string[], options: { all: boolean; rule: string[] }) => { + if (!(options.all || options.rule)) { + throw new Error('Please specify at least one rule to suppress'); + } + + const eslintCLI = whichEslint(); + + const env = { ...process.env }; + if (options.all) { + env.ESLINT_BULK_SUPPRESS = '*'; + } else if (options.rule) { + env.ESLINT_BULK_SUPPRESS = options.rule.join(','); + } + + exec( + `${eslintCLI} ${files.join(' ')} --format=json`, + { env }, + (error: ExecException | null, stdout: string, stderr: string) => { + // if errorCount != 0, ESLint will process.exit(1) giving the false impression + // that the exec failed, even though linting errors are to be expected + const eslintOutputWithErrorRegex = /"errorCount":(?!0)\d+/; + const isEslintError = error !== null && error.code === 1 && eslintOutputWithErrorRegex.test(stdout); + + if (error && !isEslintError) { + console.error(`Execution error: ${error.message}`); + process.exit(1); + } + + if (stderr) { + console.error(`ESLint errors: ${stderr}`); + process.exit(1); + } + + if (options.all) { + console.log(`Successfully suppressed all rules for file(s) ${files.join(',')}`); + } else if (options.rule) { + console.log( + `Successfully suppressed rules ${options.rule.join(',')} for file(s) ${files.join(',')}` + ); + } + } + ); + }); + return suppress; +} diff --git a/eslint/eslint-bulk/src/utils/find-patch.ts b/eslint/eslint-bulk/src/utils/find-patch.ts new file mode 100644 index 00000000000..c2094297a0e --- /dev/null +++ b/eslint/eslint-bulk/src/utils/find-patch.ts @@ -0,0 +1,30 @@ +import { execSync } from 'child_process'; +import { promisify } from 'util'; + +const exec = promisify(execSync); + +export function findPatch(): string { + try { + const env = { ...process.env, ESLINT_BULK_FIND: 'true' }; + const stdout = execSync('echo "" | eslint --stdin --no-eslintrc', { env, stdio: 'pipe' }); + + console.log('stdout HERE:', stdout); + + const startDelimiter = 'ESLINT_BULK_STDOUT_START'; + const endDelimiter = 'ESLINT_BULK_STDOUT_END'; + + const regex = new RegExp(`${startDelimiter}(.*?)${endDelimiter}`); + const match = stdout.toString().match(regex); + + if (match) { + const filePath = match[1].trim(); + return filePath; + } + + throw new Error( + 'Error finding patch path. Are you sure the eslint-bulk is installed in the package(s) that you are trying to lint?' + ); + } catch (e: unknown) { + throw new Error('Error finding patch path: ' + (e as Error).message); + } +} diff --git a/eslint/eslint-bulk/src/utils/wrapWordsToLines.ts b/eslint/eslint-bulk/src/utils/wrapWordsToLines.ts new file mode 100644 index 00000000000..5c166672cf7 --- /dev/null +++ b/eslint/eslint-bulk/src/utils/wrapWordsToLines.ts @@ -0,0 +1,104 @@ +/** + * Applies word wrapping and returns an array of lines. + * + * @param text - The text to wrap + * @param maxLineLength - The maximum length of a line, defaults to the console width + * @param indent - The number of spaces to indent the wrapped lines, defaults to 0 + */ + +export function wrapWordsToLines(text: string, maxLineLength?: number, indent?: number): string[]; +/** + * Applies word wrapping and returns an array of lines. + * + * @param text - The text to wrap + * @param maxLineLength - The maximum length of a line, defaults to the console width + * @param linePrefix - The string to prefix each line with, defaults to '' + */ +export function wrapWordsToLines(text: string, maxLineLength?: number, linePrefix?: string): string[]; +/** + * Applies word wrapping and returns an array of lines. + * + * @param text - The text to wrap + * @param maxLineLength - The maximum length of a line, defaults to the console width + * @param indentOrLinePrefix - The number of spaces to indent the wrapped lines or the string to prefix + * each line with, defaults to no prefix + */ +export function wrapWordsToLines( + text: string, + maxLineLength?: number, + indentOrLinePrefix?: number | string +): string[]; +export function wrapWordsToLines( + text: string, + maxLineLength?: number, + indentOrLinePrefix?: number | string +): string[] { + let linePrefix: string; + switch (typeof indentOrLinePrefix) { + case 'number': + linePrefix = ' '.repeat(indentOrLinePrefix); + break; + case 'string': + linePrefix = indentOrLinePrefix; + break; + default: + linePrefix = ''; + break; + } + + const linePrefixLength: number = linePrefix.length; + + if (!maxLineLength) { + maxLineLength = process.stdout.getWindowSize()[0]; + } + + // Apply word wrapping and the provided line prefix, while also respecting existing newlines + // and prefix spaces that may exist in the text string already. + const lines: string[] = text.split(/\r?\n/); + + const wrappedLines: string[] = []; + for (const line of lines) { + if (line.length + linePrefixLength <= maxLineLength) { + wrappedLines.push(linePrefix + line); + } else { + const lineAdditionalPrefix: string = line.match(/^\s*/)?.[0] || ''; + const whitespaceRegexp: RegExp = /\s+/g; + let currentWhitespaceMatch: RegExpExecArray | null = null; + let previousWhitespaceMatch: RegExpExecArray | undefined; + let currentLineStartIndex: number = lineAdditionalPrefix.length; + let previousBreakRanOver: boolean = false; + while ((currentWhitespaceMatch = whitespaceRegexp.exec(line)) !== null) { + if (currentWhitespaceMatch.index + linePrefixLength - currentLineStartIndex > maxLineLength) { + let whitespaceToSplitAt: RegExpExecArray | undefined; + if ( + !previousWhitespaceMatch || + // Handle the case where there are two words longer than the maxLineLength in a row + previousBreakRanOver + ) { + whitespaceToSplitAt = currentWhitespaceMatch; + } else { + whitespaceToSplitAt = previousWhitespaceMatch; + } + + wrappedLines.push( + linePrefix + + lineAdditionalPrefix + + line.substring(currentLineStartIndex, whitespaceToSplitAt.index) + ); + previousBreakRanOver = whitespaceToSplitAt.index - currentLineStartIndex > maxLineLength; + currentLineStartIndex = whitespaceToSplitAt.index + whitespaceToSplitAt[0].length; + } else { + previousBreakRanOver = false; + } + + previousWhitespaceMatch = currentWhitespaceMatch; + } + + if (currentLineStartIndex < line.length) { + wrappedLines.push(linePrefix + lineAdditionalPrefix + line.substring(currentLineStartIndex)); + } + } + } + + return wrappedLines; +}