From 70a62e75761bca3648c08abf70aff2b4c6b76a55 Mon Sep 17 00:00:00 2001 From: Daniel Nadeau <3473356+D4N14L@users.noreply.github.com> Date: Tue, 13 Aug 2024 10:56:06 -0700 Subject: [PATCH 1/6] Nit: docs and cleanup --- heft-plugins/heft-lint-plugin/src/Eslint.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/heft-plugins/heft-lint-plugin/src/Eslint.ts b/heft-plugins/heft-lint-plugin/src/Eslint.ts index 2f79618d611..2264c609364 100644 --- a/heft-plugins/heft-lint-plugin/src/Eslint.ts +++ b/heft-plugins/heft-lint-plugin/src/Eslint.ts @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import * as path from 'path'; import * as crypto from 'crypto'; import * as semver from 'semver'; import type * as TTypescript from 'typescript'; @@ -26,8 +25,10 @@ enum EslintMessageSeverity { error = 2 } +// Patch the timer used to track rule execution time. This allows us to get access to the detailed information +// about how long each rule took to execute, which we provide on the CLI when running in verbose mode. async function patchTimerAsync(eslintPackagePath: string, timingsMap: Map): Promise { - const timingModulePath: string = path.join(eslintPackagePath, 'lib', 'linter', 'timing'); + const timingModulePath: string = `${eslintPackagePath}/lib/linter/timing`; const timing: IEslintTiming = (await import(timingModulePath)).default; timing.enabled = true; const patchedTime: (key: string, fn: (...args: unknown[]) => unknown) => (...args: unknown[]) => unknown = ( From 2e178a9056dbb031f5287e8995dddb8f31d84895 Mon Sep 17 00:00:00 2001 From: Daniel Nadeau <3473356+D4N14L@users.noreply.github.com> Date: Tue, 13 Aug 2024 11:26:39 -0700 Subject: [PATCH 2/6] Add fix functionality to tslint --- .../heft-lint-plugin/heft-plugin.json | 12 +++++- .../heft-lint-plugin/src/LintPlugin.ts | 43 +++++++++++++------ .../heft-lint-plugin/src/LinterBase.ts | 11 +++++ heft-plugins/heft-lint-plugin/src/Tslint.ts | 18 +++++++- .../src/internalTypings/TslintInternals.ts | 15 ++++++- .../src/schemas/heft-lint-plugin.schema.json | 16 +++++++ 6 files changed, 100 insertions(+), 15 deletions(-) create mode 100644 heft-plugins/heft-lint-plugin/src/schemas/heft-lint-plugin.schema.json diff --git a/heft-plugins/heft-lint-plugin/heft-plugin.json b/heft-plugins/heft-lint-plugin/heft-plugin.json index ed7be99c522..d905b684469 100644 --- a/heft-plugins/heft-lint-plugin/heft-plugin.json +++ b/heft-plugins/heft-lint-plugin/heft-plugin.json @@ -4,7 +4,17 @@ "taskPlugins": [ { "pluginName": "lint-plugin", - "entryPoint": "./lib/LintPlugin" + "entryPoint": "./lib/LintPlugin", + "optionsSchema": "./lib/schemas/heft-lint-plugin.schema.json", + + "parameterScope": "lint", + "parameters": [ + { + "longName": "--fix", + "parameterKind": "flag", + "description": "Fix all encountered rule violations where the violated rule provides a fixer." + } + ] } ] } diff --git a/heft-plugins/heft-lint-plugin/src/LintPlugin.ts b/heft-plugins/heft-lint-plugin/src/LintPlugin.ts index 6d46febf00a..0f4b580b87a 100644 --- a/heft-plugins/heft-lint-plugin/src/LintPlugin.ts +++ b/heft-plugins/heft-lint-plugin/src/LintPlugin.ts @@ -22,10 +22,23 @@ import type { IExtendedProgram, IExtendedSourceFile } from './internalTypings/Ty const PLUGIN_NAME: 'lint-plugin' = 'lint-plugin'; const TYPESCRIPT_PLUGIN_NAME: typeof TypeScriptPluginName = 'typescript-plugin'; +const FIX_PARAMETER_NAME: string = '--fix'; const ESLINTRC_JS_FILENAME: string = '.eslintrc.js'; const ESLINTRC_CJS_FILENAME: string = '.eslintrc.cjs'; -export default class LintPlugin implements IHeftTaskPlugin { +interface ILintPluginOptions { + alwaysFix?: boolean; +} + +interface ILintOptions { + taskSession: IHeftTaskSession; + heftConfiguration: HeftConfiguration; + tsProgram: IExtendedProgram; + fix?: boolean; + changedFiles?: ReadonlySet; +} + +export default class LintPlugin implements IHeftTaskPlugin { private readonly _lintingPromises: Promise[] = []; // These are initliazed by _initAsync @@ -35,7 +48,11 @@ export default class LintPlugin implements IHeftTaskPlugin { private _tslintToolPath: string | undefined; private _tslintConfigFilePath: string | undefined; - public apply(taskSession: IHeftTaskSession, heftConfiguration: HeftConfiguration): void { + public apply( + taskSession: IHeftTaskSession, + heftConfiguration: HeftConfiguration, + pluginOptions?: ILintPluginOptions + ): void { // Disable linting in watch mode. Some lint rules require the context of multiple files, which // may not be available in watch mode. if (!taskSession.parameters.watch) { @@ -48,12 +65,15 @@ export default class LintPlugin implements IHeftTaskPlugin { accessor.onChangedFilesHook.tap( PLUGIN_NAME, (changedFilesHookOptions: IChangedFilesHookOptions) => { - const lintingPromise: Promise = this._lintAsync( + const lintingPromise: Promise = this._lintAsync({ taskSession, heftConfiguration, - changedFilesHookOptions.program as IExtendedProgram, - changedFilesHookOptions.changedFiles as ReadonlySet - ); + fix: + pluginOptions?.alwaysFix || + taskSession.parameters.getFlagParameter(FIX_PARAMETER_NAME).value, + tsProgram: changedFilesHookOptions.program as IExtendedProgram, + changedFiles: changedFilesHookOptions.changedFiles as ReadonlySet + }); lintingPromise.catch(() => { // Suppress unhandled promise rejection error }); @@ -114,12 +134,9 @@ export default class LintPlugin implements IHeftTaskPlugin { } } - private async _lintAsync( - taskSession: IHeftTaskSession, - heftConfiguration: HeftConfiguration, - tsProgram: IExtendedProgram, - changedFiles?: ReadonlySet - ): Promise { + private async _lintAsync(options: ILintOptions): Promise { + const { taskSession, heftConfiguration, tsProgram, changedFiles, fix } = options; + // Ensure that we have initialized. This promise is cached, so calling init // multiple times will only init once. await this._ensureInitializedAsync(taskSession, heftConfiguration); @@ -128,6 +145,7 @@ export default class LintPlugin implements IHeftTaskPlugin { if (this._eslintConfigFilePath && this._eslintToolPath) { const eslintLinter: Eslint = await Eslint.initializeAsync({ tsProgram, + fix, scopedLogger: taskSession.logger, linterToolPath: this._eslintToolPath, linterConfigFilePath: this._eslintConfigFilePath, @@ -140,6 +158,7 @@ export default class LintPlugin implements IHeftTaskPlugin { if (this._tslintConfigFilePath && this._tslintToolPath) { const tslintLinter: Tslint = await Tslint.initializeAsync({ tsProgram, + fix, scopedLogger: taskSession.logger, linterToolPath: this._tslintToolPath, linterConfigFilePath: this._tslintConfigFilePath, diff --git a/heft-plugins/heft-lint-plugin/src/LinterBase.ts b/heft-plugins/heft-lint-plugin/src/LinterBase.ts index 4a6f7fa6086..cdb89eed547 100644 --- a/heft-plugins/heft-lint-plugin/src/LinterBase.ts +++ b/heft-plugins/heft-lint-plugin/src/LinterBase.ts @@ -20,6 +20,7 @@ export interface ILinterBaseOptions { linterToolPath: string; linterConfigFilePath: string; tsProgram: IExtendedProgram; + fix?: boolean; } export interface IRunLinterOptions { @@ -56,6 +57,9 @@ export abstract class LinterBase { protected readonly _buildFolderPath: string; protected readonly _buildMetadataFolderPath: string; protected readonly _linterConfigFilePath: string; + protected readonly _fix: boolean; + + protected _fixesPossible: boolean = false; private readonly _linterName: string; @@ -66,6 +70,7 @@ export abstract class LinterBase { this._buildMetadataFolderPath = options.buildMetadataFolderPath; this._linterConfigFilePath = options.linterConfigFilePath; this._linterName = linterName; + this._fix = options.fix || false; } public abstract printVersionHeader(): void; @@ -158,6 +163,12 @@ export abstract class LinterBase { this.lintingFinished(lintFailures); + if (!this._fix && this._fixesPossible) { + this._terminal.writeWarningLine( + 'The linter reported that fixes are possible. To apply fixes, run Heft with the "--fix" option.' + ); + } + const updatedTslintCacheData: ILinterCacheData = { cacheVersion: linterCacheVersion, fileVersions: Array.from(newNoFailureFileVersions) diff --git a/heft-plugins/heft-lint-plugin/src/Tslint.ts b/heft-plugins/heft-lint-plugin/src/Tslint.ts index 2a95013a7f7..7b12e3f4db9 100644 --- a/heft-plugins/heft-lint-plugin/src/Tslint.ts +++ b/heft-plugins/heft-lint-plugin/src/Tslint.ts @@ -137,7 +137,14 @@ export class Tslint extends LinterBase { // Some of this code comes from here: // https://github.com/palantir/tslint/blob/24d29e421828348f616bf761adb3892bcdf51662/src/linter.ts#L161-L179 // Modified to only lint files that have changed and that we care about - const failures: TTslint.RuleFailure[] = this._linter.getAllFailures(sourceFile, this._enabledRules); + let failures: TTslint.RuleFailure[] = this._linter.getAllFailures(sourceFile, this._enabledRules); + if (failures.some((f) => f.hasFix())) { + if (this._fix) { + failures = this._linter.applyAllFixes(this._enabledRules, failures, sourceFile, sourceFile.fileName); + } else { + this._fixesPossible = true; + } + } for (const failure of failures) { const severity: TTslint.RuleSeverity | undefined = this._ruleSeverityMap.get(failure.getRuleName()); @@ -155,6 +162,15 @@ export class Tslint extends LinterBase { this._linter.failures = failures; const lintResult: TTslint.LintResult = this._linter.getResult(); + // Report linter fixes to the logger. These will only be returned when the underlying failure was fixed + if (lintResult.fixes?.length) { + for (const fixedTslintFailure of lintResult.fixes) { + const formattedMessage: string = `[FIXED] ${getFormattedErrorMessage(fixedTslintFailure)}`; + const errorObject: FileError = this._getLintFileError(fixedTslintFailure, formattedMessage); + this._scopedLogger.emitWarning(errorObject); + } + } + // Report linter errors and warnings to the logger if (lintResult.failures.length) { for (const tslintFailure of lintResult.failures) { diff --git a/heft-plugins/heft-lint-plugin/src/internalTypings/TslintInternals.ts b/heft-plugins/heft-lint-plugin/src/internalTypings/TslintInternals.ts index e99f518f605..a886be74f73 100644 --- a/heft-plugins/heft-lint-plugin/src/internalTypings/TslintInternals.ts +++ b/heft-plugins/heft-lint-plugin/src/internalTypings/TslintInternals.ts @@ -4,7 +4,10 @@ import type * as TTslint from 'tslint'; import type * as TTypescript from 'typescript'; -type TrimmedLinter = Omit; +type TrimmedLinter = Omit< + TTslint.Linter, + 'getAllFailures' | 'applyAllFixes' | 'getEnabledRules' | 'failures' +>; export interface IExtendedLinter extends TrimmedLinter { /** * https://github.com/palantir/tslint/blob/24d29e421828348f616bf761adb3892bcdf51662/src/linter.ts#L117 @@ -20,4 +23,14 @@ export interface IExtendedLinter extends TrimmedLinter { * https://github.com/palantir/tslint/blob/24d29e421828348f616bf761adb3892bcdf51662/src/linter.ts#L303-L306 */ getEnabledRules(configuration: TTslint.Configuration.IConfigurationFile, isJs: boolean): TTslint.IRule[]; + + /** + * https://github.com/palantir/tslint/blob/24d29e421828348f616bf761adb3892bcdf51662/src/linter.ts#L212-L241 + */ + applyAllFixes( + enabledRules: TTslint.IRule[], + fileFailures: TTslint.RuleFailure[], + sourceFile: TTypescript.SourceFile, + sourceFileName: string + ): TTslint.RuleFailure[]; } diff --git a/heft-plugins/heft-lint-plugin/src/schemas/heft-lint-plugin.schema.json b/heft-plugins/heft-lint-plugin/src/schemas/heft-lint-plugin.schema.json new file mode 100644 index 00000000000..3e5ccdd14d4 --- /dev/null +++ b/heft-plugins/heft-lint-plugin/src/schemas/heft-lint-plugin.schema.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Heft Lint Plugin Options Configuration", + "description": "This schema describes the \"options\" field that can be specified in heft.json when loading \"@rushstack/heft-lint-plugin\".", + "type": "object", + + "additionalProperties": false, + + "properties": { + "alwaysFix": { + "title": "Always Fix", + "description": "If set to true, fix all encountered rule violations where the violated rule provides a fixer, regardless of if the \"--fix\" command-line argument is provided.", + "type": "boolean" + } + } +} From 807cc5a09cf12ee3520170c746060e47c010ef7a Mon Sep 17 00:00:00 2001 From: Daniel Nadeau <3473356+D4N14L@users.noreply.github.com> Date: Tue, 13 Aug 2024 17:30:09 -0700 Subject: [PATCH 3/6] Add support for --fix in ESLint --- heft-plugins/heft-lint-plugin/src/Eslint.ts | 132 +++++++++++++----- .../heft-lint-plugin/src/LinterBase.ts | 4 +- heft-plugins/heft-lint-plugin/src/Tslint.ts | 29 ++-- 3 files changed, 112 insertions(+), 53 deletions(-) diff --git a/heft-plugins/heft-lint-plugin/src/Eslint.ts b/heft-plugins/heft-lint-plugin/src/Eslint.ts index 2264c609364..e3b09c07830 100644 --- a/heft-plugins/heft-lint-plugin/src/Eslint.ts +++ b/heft-plugins/heft-lint-plugin/src/Eslint.ts @@ -47,26 +47,55 @@ async function patchTimerAsync(eslintPackagePath: string, timingsMap: Map { private readonly _eslintPackage: typeof TEslint; private readonly _linter: TEslint.ESLint; private readonly _eslintTimings: Map = new Map(); + private readonly _currentFixMessages: TEslint.Linter.LintMessage[] = []; + private readonly _fixMessagesByResult: Map = + new Map(); protected constructor(options: IEslintOptions) { super('eslint', options); - const { buildFolderPath, eslintPackage, linterConfigFilePath, tsProgram, eslintTimings } = options; + const { buildFolderPath, eslintPackage, linterConfigFilePath, tsProgram, eslintTimings, fix } = options; this._eslintPackage = eslintPackage; - this._linter = new eslintPackage.ESLint({ - cwd: buildFolderPath, - overrideConfigFile: linterConfigFilePath, - // Override config takes precedence over overrideConfigFile, which allows us to provide - // the source TypeScript program to ESLint - overrideConfig: { + + let overrideConfig: TEslint.Linter.Config | undefined; + let fixFn: Exclude; + if (fix) { + // We do not recieve the messages for the issues that were fixed, so we need to track them ourselves + // so that we can log them after the fix is applied. This array will be populated by the fix function, + // and subsequently mapped to the results in the ESLint.lintFileAsync method below. After the messages + // are mapped, the array will be cleared so that it is ready for the next fix operation. + fixFn = (message: TEslint.Linter.LintMessage) => { + this._currentFixMessages.push(message); + return true; + }; + } else { + // The @typescript-eslint/parser package allows providing an existing TypeScript program to avoid needing + // to reparse. However, fixers in ESLint run in multiple passes against the underlying code until the + // fix fully succeeds. This conflicts with providing an existing program as the code no longer maps to + // the provided program, producing garbage fix output. To avoid this, only provide the existing program + // if we're not fixing. + overrideConfig = { parserOptions: { programs: [tsProgram] } - } + }; + } + + this._linter = new eslintPackage.ESLint({ + cwd: buildFolderPath, + overrideConfigFile: linterConfigFilePath, + // Override config takes precedence over overrideConfigFile + overrideConfig, + fix: fixFn }); this._eslintTimings = eslintTimings; } @@ -122,9 +151,27 @@ export class Eslint extends LinterBase { filePath: sourceFile.fileName }); + // Map the fix messages to the results. This API should only return one result per file, so we can be sure + // that the fix messages belong to the returned result. If we somehow receive multiple results, we will + // drop the messages on the floor, but since they are only used for logging, this should not be a problem. + const fixMessages: TEslint.Linter.LintMessage[] = this._currentFixMessages.splice( + 0, + this._currentFixMessages.length + ); + if (lintResults.length === 1) { + this._fixMessagesByResult.set(lintResults[0], fixMessages); + } + + this._fixesPossible = + this._fixesPossible || + (!this._fix && + lintResults.some((lintResult: TEslint.ESLint.LintResult) => { + return lintResult.fixableErrorCount + lintResult.fixableWarningCount > 0; + })); + const failures: TEslint.ESLint.LintResult[] = []; for (const lintResult of lintResults) { - if (lintResult.messages.length > 0) { + if (lintResult.messages.length > 0 || lintResult.output) { failures.push(lintResult); } } @@ -132,7 +179,7 @@ export class Eslint extends LinterBase { return failures; } - protected lintingFinished(lintFailures: TEslint.ESLint.LintResult[]): void { + protected async lintingFinishedAsync(lintFailures: TEslint.ESLint.LintResult[]): Promise { let omittedRuleCount: number = 0; const timings: [string, number][] = Array.from(this._eslintTimings).sort( (x: [string, number], y: [string, number]) => { @@ -151,45 +198,58 @@ export class Eslint extends LinterBase { this._terminal.writeVerboseLine(`${omittedRuleCount} rules took 0ms`); } - const errors: Error[] = []; - const warnings: Error[] = []; - - for (const eslintFailure of lintFailures) { - for (const message of eslintFailure.messages) { - // https://eslint.org/docs/developer-guide/nodejs-api#◆-lintmessage-type - const formattedMessage: string = message.ruleId - ? `(${message.ruleId}) ${message.message}` - : message.message; - const errorObject: FileError = new FileError(formattedMessage, { - absolutePath: eslintFailure.filePath, - projectFolder: this._buildFolderPath, - line: message.line, - column: message.column - }); - switch (message.severity) { + if (this._fix && this._fixMessagesByResult.size > 0) { + await this._eslintPackage.ESLint.outputFixes(lintFailures); + } + + for (const lintFailure of lintFailures) { + // Report linter fixes to the logger. These will only be returned when the underlying failure was fixed + const fixMessages: TEslint.Linter.LintMessage[] | undefined = + this._fixMessagesByResult.get(lintFailure); + if (fixMessages) { + for (const fixMessage of fixMessages) { + const formattedMessage: string = `[FIXED] ${getFormattedErrorMessage(fixMessage)}`; + const errorObject: FileError = this._getLintFileError(lintFailure, fixMessage, formattedMessage); + this._scopedLogger.emitWarning(errorObject); + } + } + + // Report linter errors and warnings to the logger + for (const lintMessage of lintFailure.messages) { + const errorObject: FileError = this._getLintFileError(lintFailure, lintMessage); + switch (lintMessage.severity) { case EslintMessageSeverity.error: { - errors.push(errorObject); + this._scopedLogger.emitError(errorObject); break; } case EslintMessageSeverity.warning: { - warnings.push(errorObject); + this._scopedLogger.emitWarning(errorObject); break; } } } } - - for (const error of errors) { - this._scopedLogger.emitError(error); - } - - for (const warning of warnings) { - this._scopedLogger.emitWarning(warning); - } } protected async isFileExcludedAsync(filePath: string): Promise { return await this._linter.isPathIgnored(filePath); } + + private _getLintFileError( + lintResult: TEslint.ESLint.LintResult, + lintMessage: TEslint.Linter.LintMessage, + message?: string + ): FileError { + if (!message) { + message = getFormattedErrorMessage(lintMessage); + } + + return new FileError(message, { + absolutePath: lintResult.filePath, + projectFolder: this._buildFolderPath, + line: lintMessage.line, + column: lintMessage.column + }); + } } diff --git a/heft-plugins/heft-lint-plugin/src/LinterBase.ts b/heft-plugins/heft-lint-plugin/src/LinterBase.ts index cdb89eed547..fe996d47e1f 100644 --- a/heft-plugins/heft-lint-plugin/src/LinterBase.ts +++ b/heft-plugins/heft-lint-plugin/src/LinterBase.ts @@ -161,7 +161,7 @@ export abstract class LinterBase { } //#endregion - this.lintingFinished(lintFailures); + await this.lintingFinishedAsync(lintFailures); if (!this._fix && this._fixesPossible) { this._terminal.writeWarningLine( @@ -184,7 +184,7 @@ export abstract class LinterBase { protected abstract lintFileAsync(sourceFile: IExtendedSourceFile): Promise; - protected abstract lintingFinished(lintFailures: TLintResult[]): void; + protected abstract lintingFinishedAsync(lintFailures: TLintResult[]): Promise; protected abstract isFileExcludedAsync(filePath: string): Promise; } diff --git a/heft-plugins/heft-lint-plugin/src/Tslint.ts b/heft-plugins/heft-lint-plugin/src/Tslint.ts index 7b12e3f4db9..cfe108c0c79 100644 --- a/heft-plugins/heft-lint-plugin/src/Tslint.ts +++ b/heft-plugins/heft-lint-plugin/src/Tslint.ts @@ -138,7 +138,8 @@ export class Tslint extends LinterBase { // https://github.com/palantir/tslint/blob/24d29e421828348f616bf761adb3892bcdf51662/src/linter.ts#L161-L179 // Modified to only lint files that have changed and that we care about let failures: TTslint.RuleFailure[] = this._linter.getAllFailures(sourceFile, this._enabledRules); - if (failures.some((f) => f.hasFix())) { + const hasFixableIssue: boolean = failures.some((f) => f.hasFix()); + if (hasFixableIssue) { if (this._fix) { failures = this._linter.applyAllFixes(this._enabledRules, failures, sourceFile, sourceFile.fileName); } else { @@ -158,7 +159,7 @@ export class Tslint extends LinterBase { return failures; } - protected lintingFinished(failures: TTslint.RuleFailure[]): void { + protected async lintingFinishedAsync(failures: TTslint.RuleFailure[]): Promise { this._linter.failures = failures; const lintResult: TTslint.LintResult = this._linter.getResult(); @@ -172,19 +173,17 @@ export class Tslint extends LinterBase { } // Report linter errors and warnings to the logger - if (lintResult.failures.length) { - for (const tslintFailure of lintResult.failures) { - const errorObject: FileError = this._getLintFileError(tslintFailure); - switch (tslintFailure.getRuleSeverity()) { - case 'error': { - this._scopedLogger.emitError(errorObject); - break; - } - - case 'warning': { - this._scopedLogger.emitWarning(errorObject); - break; - } + for (const tslintFailure of lintResult.failures) { + const errorObject: FileError = this._getLintFileError(tslintFailure); + switch (tslintFailure.getRuleSeverity()) { + case 'error': { + this._scopedLogger.emitError(errorObject); + break; + } + + case 'warning': { + this._scopedLogger.emitWarning(errorObject); + break; } } } From e043ac7b08ff4bd16dc31be5d2d485fe1290f69a Mon Sep 17 00:00:00 2001 From: Daniel Nadeau <3473356+D4N14L@users.noreply.github.com> Date: Tue, 13 Aug 2024 17:32:51 -0700 Subject: [PATCH 4/6] Rush change --- .../user-danade-LintFixer_2024-08-14-00-32.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 common/changes/@rushstack/heft-lint-plugin/user-danade-LintFixer_2024-08-14-00-32.json diff --git a/common/changes/@rushstack/heft-lint-plugin/user-danade-LintFixer_2024-08-14-00-32.json b/common/changes/@rushstack/heft-lint-plugin/user-danade-LintFixer_2024-08-14-00-32.json new file mode 100644 index 00000000000..dff7467c76c --- /dev/null +++ b/common/changes/@rushstack/heft-lint-plugin/user-danade-LintFixer_2024-08-14-00-32.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/heft-lint-plugin", + "comment": "Add autofix functionality for ESLint and TSLint. Fixes can now be applied by providing the \"--fix\" command-line argument, or setting the \"alwaysFix\" plugin option to \"true\"", + "type": "minor" + } + ], + "packageName": "@rushstack/heft-lint-plugin" +} \ No newline at end of file From a34c4502edd4f7062624fd0ac70cd9e9a0222473 Mon Sep 17 00:00:00 2001 From: Daniel <3473356+D4N14L@users.noreply.github.com> Date: Wed, 14 Aug 2024 11:29:59 -0700 Subject: [PATCH 5/6] Update heft-plugins/heft-lint-plugin/src/Eslint.ts Co-authored-by: David Michon --- heft-plugins/heft-lint-plugin/src/Eslint.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/heft-plugins/heft-lint-plugin/src/Eslint.ts b/heft-plugins/heft-lint-plugin/src/Eslint.ts index e3b09c07830..b1dec6ec3f8 100644 --- a/heft-plugins/heft-lint-plugin/src/Eslint.ts +++ b/heft-plugins/heft-lint-plugin/src/Eslint.ts @@ -155,8 +155,7 @@ export class Eslint extends LinterBase { // that the fix messages belong to the returned result. If we somehow receive multiple results, we will // drop the messages on the floor, but since they are only used for logging, this should not be a problem. const fixMessages: TEslint.Linter.LintMessage[] = this._currentFixMessages.splice( - 0, - this._currentFixMessages.length + 0 ); if (lintResults.length === 1) { this._fixMessagesByResult.set(lintResults[0], fixMessages); From 02c30f76c0f619dcb267bc9045eb6e8006df4003 Mon Sep 17 00:00:00 2001 From: Daniel <3473356+D4N14L@users.noreply.github.com> Date: Wed, 14 Aug 2024 11:30:45 -0700 Subject: [PATCH 6/6] Update heft-plugins/heft-lint-plugin/src/Eslint.ts --- heft-plugins/heft-lint-plugin/src/Eslint.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/heft-plugins/heft-lint-plugin/src/Eslint.ts b/heft-plugins/heft-lint-plugin/src/Eslint.ts index b1dec6ec3f8..edc4603fd2f 100644 --- a/heft-plugins/heft-lint-plugin/src/Eslint.ts +++ b/heft-plugins/heft-lint-plugin/src/Eslint.ts @@ -154,9 +154,7 @@ export class Eslint extends LinterBase { // Map the fix messages to the results. This API should only return one result per file, so we can be sure // that the fix messages belong to the returned result. If we somehow receive multiple results, we will // drop the messages on the floor, but since they are only used for logging, this should not be a problem. - const fixMessages: TEslint.Linter.LintMessage[] = this._currentFixMessages.splice( - 0 - ); + const fixMessages: TEslint.Linter.LintMessage[] = this._currentFixMessages.splice(0); if (lintResults.length === 1) { this._fixMessagesByResult.set(lintResults[0], fixMessages); }