From 22f52fd82bbbe306bc9612ebcd74ec2c15d70e8b Mon Sep 17 00:00:00 2001 From: Jag Jayaprakash Date: Fri, 31 May 2024 14:22:37 -0700 Subject: [PATCH 1/9] NEW (Extension) @W-13991634@ Add class level PMD violations --- src/lib/fixer.ts | 47 +++++++++++++++++++++++++++++++++++++++++++++ src/lib/messages.ts | 3 ++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/lib/fixer.ts b/src/lib/fixer.ts index 6d84bfb..ac99349 100644 --- a/src/lib/fixer.ts +++ b/src/lib/fixer.ts @@ -98,6 +98,7 @@ export class _PmdFixGenerator extends FixGenerator { const fixes: vscode.CodeAction[] = []; if (this.documentSupportsLineLevelSuppression()) { fixes.push(this.generateLineLevelSuppression()); + fixes.push(this.generateClassLevelSuppression()); } return fixes; } @@ -133,4 +134,50 @@ export class _PmdFixGenerator extends FixGenerator { }; return action; } + + /** + * + * @returns An action that will apply a line-level suppression to the targeted diagnostic. + */ + private generateClassLevelSuppression(): vscode.CodeAction { + // Create a position indicating the very end of the violation's start line. + // const endOfLine: vscode.Position = new vscode.Position(this.diagnostic.range.start.line, Number.MAX_SAFE_INTEGER); + const classStartPosition = this.findClassEndOfLinePosition(this.diagnostic); + + const action = new vscode.CodeAction(messages.fixer.supressOnClass, vscode.CodeActionKind.QuickFix); + action.edit = new vscode.WorkspaceEdit(); + action.edit.insert(this.document.uri, classStartPosition, " // @SuppressWarnings('PMD') "); + action.diagnostics = [this.diagnostic]; + return action; + } + + /** + * Finds the end of the line position of the class in the document where the diagnostic is located. + * @returns The position at the end of the line of the class. + */ + private findClassEndOfLinePosition(diagnostic: vscode.Diagnostic): vscode.Position { + const text = this.document.getText(); + const diagnosticLine = diagnostic.range.start.line; + + // Split the text into lines for easier processing + const lines = text.split('\n'); + let classStartLine: number | undefined; + + // Iterate from the diagnostic line upwards to find the class declaration + for (let lineNumber = diagnosticLine; lineNumber >= 0; lineNumber--) { + const line = lines[lineNumber]; + if (line.includes(' class ')) { + classStartLine = lineNumber; + break; + } + } + + if (classStartLine !== undefined) { + const classLineText = lines[classStartLine]; + return new vscode.Position(classStartLine, classLineText.length); + } + + // Default to the start of the document if class is not found + return new vscode.Position(0, 0); +} } diff --git a/src/lib/messages.ts b/src/lib/messages.ts index fc32ddf..2df5ea2 100644 --- a/src/lib/messages.ts +++ b/src/lib/messages.ts @@ -32,7 +32,8 @@ export const messages = { existingDfaRunText: "A Salesforce Graph Engine analysis is already running. Cancel it by clicking in the Status Bar.", }, fixer: { - supressOnLine: "Suppress violations on this line." + supressOnLine: "Suppress violations on this line.", + supressOnClass: "***Suppress violations on this class.***" }, diagnostics: { messageGenerator: (severity: number, message: string) => `Sev${severity}: ${message}`, From a5ff50b8993e94a5d26c2a94e2928a8f332ae643 Mon Sep 17 00:00:00 2001 From: Jag Jayaprakash Date: Mon, 3 Jun 2024 11:32:30 -0700 Subject: [PATCH 2/9] NEW (Extension) @W-13991634@ Add class level PMD violations - part 2 --- src/lib/fixer.ts | 118 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 85 insertions(+), 33 deletions(-) diff --git a/src/lib/fixer.ts b/src/lib/fixer.ts index ac99349..0d499d2 100644 --- a/src/lib/fixer.ts +++ b/src/lib/fixer.ts @@ -132,52 +132,104 @@ export class _PmdFixGenerator extends FixGenerator { title: 'Clear Single Diagnostic', arguments: [this.document.uri, this.diagnostic] }; + return action; } - /** - * - * @returns An action that will apply a line-level suppression to the targeted diagnostic. - */ private generateClassLevelSuppression(): vscode.CodeAction { - // Create a position indicating the very end of the violation's start line. - // const endOfLine: vscode.Position = new vscode.Position(this.diagnostic.range.start.line, Number.MAX_SAFE_INTEGER); - const classStartPosition = this.findClassEndOfLinePosition(this.diagnostic); + // Find the end-of-line position of the class declaration where the diagnostic is found. + const classStartPosition = this.findClassStartPosition(this.diagnostic, this.document); + // const classEndOfLinePosition = 0; const action = new vscode.CodeAction(messages.fixer.supressOnClass, vscode.CodeActionKind.QuickFix); action.edit = new vscode.WorkspaceEdit(); - action.edit.insert(this.document.uri, classStartPosition, " // @SuppressWarnings('PMD') "); + + // Determine the appropriate suppression rule based on the type of diagnostic.code + let suppressionRule: string; + if (typeof this.diagnostic.code === 'object' && this.diagnostic.code !== null && 'value' in this.diagnostic.code) { + suppressionRule = `PMD.${this.diagnostic.code.value}`; + } else { + suppressionRule = `PMD`; + } + + // Extract text from the start to end of the class declaration to search for existing suppressions + const classText = this.findLineBeforeClassStartDeclaration(classStartPosition); + const suppressionRegex = /@SuppressWarnings\s*\(\s*'([^']*)'\s*\)/; + const suppressionMatch = classText.match(suppressionRegex); + + if (suppressionMatch) { + // If @SuppressWarnings exists, check if the rule is already present + const existingRules = suppressionMatch[1].split(',').map(rule => rule.trim()); + if (!existingRules.includes(suppressionRule)) { + // If the rule is not present, add it to the existing @SuppressWarnings + const updatedRules = [...existingRules, suppressionRule].join(', '); + const updatedSuppression = `@SuppressWarnings('${updatedRules}')`; + const suppressionStartPosition = this.document.positionAt(classText.indexOf(suppressionMatch[0])); + const suppressionEndPosition = this.document.positionAt(classText.indexOf(suppressionMatch[0]) + suppressionMatch[0].length); + const suppressionRange = new vscode.Range(suppressionStartPosition, suppressionEndPosition); + action.edit.replace(this.document.uri, suppressionRange, updatedSuppression); + } + } else { + // If @SuppressWarnings does not exist, insert a new one + const newSuppression = `@SuppressWarnings('${suppressionRule}')\n`; + action.edit.insert(this.document.uri, classStartPosition, newSuppression); + } + action.diagnostics = [this.diagnostic]; + action.command = { + command: Constants.COMMAND_RUN_ON_SELECTED, + title: 'Re-run diagnostic for this file', + arguments: [this.document.uri] + }; + return action; } - - /** - * Finds the end of the line position of the class in the document where the diagnostic is located. - * @returns The position at the end of the line of the class. + + /** + * Finds the start position of the class in the document. + * Assumes that the class declaration starts with the keyword "class". + * @returns The position at the start of the class. */ - private findClassEndOfLinePosition(diagnostic: vscode.Diagnostic): vscode.Position { - const text = this.document.getText(); - const diagnosticLine = diagnostic.range.start.line; - - // Split the text into lines for easier processing - const lines = text.split('\n'); - let classStartLine: number | undefined; - - // Iterate from the diagnostic line upwards to find the class declaration - for (let lineNumber = diagnosticLine; lineNumber >= 0; lineNumber--) { - const line = lines[lineNumber]; - if (line.includes(' class ')) { - classStartLine = lineNumber; - break; + private findClassStartPosition(diagnostic: vscode.Diagnostic, document: vscode.TextDocument): vscode.Position { + const text = document.getText(); + const diagnosticLine = diagnostic.range.start.line; + + // Split the text into lines for easier processing + const lines = text.split('\n'); + let classStartLine: number | undefined; + + // Iterate from the diagnostic line upwards to find the class declaration + for (let lineNumber = diagnosticLine; lineNumber >= 0; lineNumber--) { + const line = lines[lineNumber]; + // if (line.includes(' class ')) { + if (line.match(/class\s+\w+/)) { + classStartLine = lineNumber; + break; + } + } + + if (classStartLine !== undefined) { + return new vscode.Position(classStartLine, 0); } + + // Default to the start of the document if class is not found + return new vscode.Position(0, 0); } - if (classStartLine !== undefined) { - const classLineText = lines[classStartLine]; - return new vscode.Position(classStartLine, classLineText.length); + /** + * Finds the entire line that is one line above a class declaration statement. + * Assumes that the class declaration starts with the keyword "class". + * @returns The text of the line that is one line above the class declaration. + */ + private findLineBeforeClassStartDeclaration(classStartPosition: vscode.Position): string { + // Ensure that there is a line before the class declaration + if (classStartPosition.line > 0) { + const lineBeforeClassPosition = classStartPosition.line - 1; + const lineBeforeClass = this.document.lineAt(lineBeforeClassPosition); + return lineBeforeClass.text; + } + + // Return an empty string if no class declaration is found or it's the first line of the document + return ''; } - - // Default to the start of the document if class is not found - return new vscode.Position(0, 0); -} } From d21b16b1ebbbc0895a35585d635781c6232e2532 Mon Sep 17 00:00:00 2001 From: Jag Jayaprakash Date: Tue, 4 Jun 2024 08:15:55 -0700 Subject: [PATCH 3/9] NEW (Extension) @W-13991634@ Add class level PMD violations - part 3 --- code-fixtures/fixer-tests/MyClass2.cls | 15 +++++++++++++ src/extension.ts | 4 ++-- src/lib/constants.ts | 2 +- src/lib/fixer.ts | 14 ++++++------ src/test/suite/fixer.test.ts | 30 +++++++++++++++++++++++++- 5 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 code-fixtures/fixer-tests/MyClass2.cls diff --git a/code-fixtures/fixer-tests/MyClass2.cls b/code-fixtures/fixer-tests/MyClass2.cls new file mode 100644 index 0000000..3376604 --- /dev/null +++ b/code-fixtures/fixer-tests/MyClass2.cls @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2023, Salesforce, Inc. + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +public class MyClass2 { + public static boolean someBooleanMethod() { + return false; + } + + public static boolean methodWithComment() { // Behold the glory of this comment. Gaze upon it. + return true; + } +} diff --git a/src/extension.ts b/src/extension.ts index 7074679..d738f76 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -85,9 +85,9 @@ export async function activate(context: vscode.ExtensionContext): Promise { + const removeDiagnosticsOnSelectedFile = vscode.commands.registerCommand(Constants.COMMAND_REMOVE_DIAGNOSTICS_ON_SELECTED_FILE, async (selection: vscode.Uri, multiSelect?: vscode.Uri[]) => { return _clearDiagnosticsForSelectedFiles(multiSelect && multiSelect.length > 0 ? multiSelect : [selection], { - commandName: Constants.COMMAND_REMOVE_DIAGNOSTRICS_ON_SELECTED_FILE, + commandName: Constants.COMMAND_REMOVE_DIAGNOSTICS_ON_SELECTED_FILE, diagnosticCollection, outputChannel }); diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 2404842..2fdd770 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -14,7 +14,7 @@ export const COMMAND_RUN_ON_ACTIVE_FILE = 'sfca.runOnActiveFile'; export const COMMAND_RUN_ON_SELECTED = 'sfca.runOnSelected'; export const COMMAND_RUN_DFA_ON_SELECTED_METHOD = 'sfca.runDfaOnSelectedMethod'; export const COMMAND_REMOVE_DIAGNOSTICS_ON_ACTIVE_FILE = 'sfca.removeDiagnosticsOnActiveFile'; -export const COMMAND_REMOVE_DIAGNOSTRICS_ON_SELECTED_FILE = 'sfca.removeDiagnosticsOnSelectedFile'; +export const COMMAND_REMOVE_DIAGNOSTICS_ON_SELECTED_FILE = 'sfca.removeDiagnosticsOnSelectedFile'; export const COMMAND_REMOVE_SINGLE_DIAGNOSTIC = 'sfca.removeSingleDiagnostic' diff --git a/src/lib/fixer.ts b/src/lib/fixer.ts index 0d499d2..5b25c0e 100644 --- a/src/lib/fixer.ts +++ b/src/lib/fixer.ts @@ -136,7 +136,7 @@ export class _PmdFixGenerator extends FixGenerator { return action; } - private generateClassLevelSuppression(): vscode.CodeAction { + public generateClassLevelSuppression(): vscode.CodeAction { // Find the end-of-line position of the class declaration where the diagnostic is found. const classStartPosition = this.findClassStartPosition(this.diagnostic, this.document); // const classEndOfLinePosition = 0; @@ -153,7 +153,7 @@ export class _PmdFixGenerator extends FixGenerator { } // Extract text from the start to end of the class declaration to search for existing suppressions - const classText = this.findLineBeforeClassStartDeclaration(classStartPosition); + const classText = this.findLineBeforeClassStartDeclaration(classStartPosition, this.document); const suppressionRegex = /@SuppressWarnings\s*\(\s*'([^']*)'\s*\)/; const suppressionMatch = classText.match(suppressionRegex); @@ -177,8 +177,8 @@ export class _PmdFixGenerator extends FixGenerator { action.diagnostics = [this.diagnostic]; action.command = { - command: Constants.COMMAND_RUN_ON_SELECTED, - title: 'Re-run diagnostic for this file', + command: Constants.COMMAND_REMOVE_DIAGNOSTICS_ON_SELECTED_FILE, + title: 'Remove diagnostics for this file', arguments: [this.document.uri] }; @@ -190,7 +190,7 @@ export class _PmdFixGenerator extends FixGenerator { * Assumes that the class declaration starts with the keyword "class". * @returns The position at the start of the class. */ - private findClassStartPosition(diagnostic: vscode.Diagnostic, document: vscode.TextDocument): vscode.Position { + public findClassStartPosition(diagnostic: vscode.Diagnostic, document: vscode.TextDocument): vscode.Position { const text = document.getText(); const diagnosticLine = diagnostic.range.start.line; @@ -221,11 +221,11 @@ export class _PmdFixGenerator extends FixGenerator { * Assumes that the class declaration starts with the keyword "class". * @returns The text of the line that is one line above the class declaration. */ - private findLineBeforeClassStartDeclaration(classStartPosition: vscode.Position): string { + public findLineBeforeClassStartDeclaration(classStartPosition: vscode.Position, document: vscode.TextDocument): string { // Ensure that there is a line before the class declaration if (classStartPosition.line > 0) { const lineBeforeClassPosition = classStartPosition.line - 1; - const lineBeforeClass = this.document.lineAt(lineBeforeClassPosition); + const lineBeforeClass = document.lineAt(lineBeforeClassPosition); return lineBeforeClass.text; } diff --git a/src/test/suite/fixer.test.ts b/src/test/suite/fixer.test.ts index 7d0aeac..2101d8b 100644 --- a/src/test/suite/fixer.test.ts +++ b/src/test/suite/fixer.test.ts @@ -7,6 +7,7 @@ import * as vscode from 'vscode'; import {expect} from 'chai'; import path = require('path'); +import Sinon = require('sinon'); import {messages} from '../../lib/messages'; import {_NoOpFixGenerator, _PmdFixGenerator} from '../../lib/fixer'; @@ -97,7 +98,7 @@ suite('fixer.ts', () => { const fixes: vscode.CodeAction[] = fixGenerator.generateFixes(); // We expect to get one fix, to inject the suppression at the end of the line. - expect(fixes).to.have.lengthOf(1, 'Wrong action count'); + expect(fixes).to.have.lengthOf(2, 'Wrong action count'); const fix = fixes[0].edit.get(fileUri)[0]; expect(fix.newText).to.equal(' // NOPMD', 'Wrong suppression added'); expect(fix.range.start.isEqual(new vscode.Position(7, Number.MAX_SAFE_INTEGER))).to.equal(true, 'Should be at the end of the violation line'); @@ -130,6 +131,33 @@ suite('fixer.ts', () => { }); */ }); + + suite('Class-level suppression', () => { + test('Find class start position above the diagnostic line', async () => { + const fileUri = vscode.Uri.file(path.join(codeFixturesPath, 'MyClass1.cls')); + + let doc: vscode.TextDocument; + doc = await vscode.workspace.openTextDocument(fileUri); + const diag = new vscode.Diagnostic( + new vscode.Range( + new vscode.Position(7, 4), + new vscode.Position(7, 10) + ), + 'This message is unimportant', + vscode.DiagnosticSeverity.Warning + ); + + // Instantiate our fixer + const fixGenerator: _PmdFixGenerator = new _PmdFixGenerator(doc, diag); + + // Call findClassStartPosition method + const position = fixGenerator.findClassStartPosition(diag, doc); + + // Verify the position is correct + expect(position.line).to.equal(6); + expect(position.character).to.equal(0); + }); + }); }); }); }); From 980c7b4881a09754fe01b0aadb3b88e4a9584e08 Mon Sep 17 00:00:00 2001 From: Jag Jayaprakash Date: Tue, 4 Jun 2024 14:37:16 -0700 Subject: [PATCH 4/9] NEW (Extension) @W-13991634@ Add class level PMD violations - part 4 --- code-fixtures/fixer-tests/MyClass2.cls | 12 +++- src/lib/fixer.ts | 54 +++++++++++++++++- src/test/suite/fixer.test.ts | 78 +++++++++++++++++++++++++- 3 files changed, 140 insertions(+), 4 deletions(-) diff --git a/code-fixtures/fixer-tests/MyClass2.cls b/code-fixtures/fixer-tests/MyClass2.cls index 3376604..de59c91 100644 --- a/code-fixtures/fixer-tests/MyClass2.cls +++ b/code-fixtures/fixer-tests/MyClass2.cls @@ -5,11 +5,21 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ public class MyClass2 { + public static boolean someBooleanMethod() { + // some comment that includes public class MyClass2 { + return false; + } + /* some other comment in a single line */ + public static boolean someOtherBooleanMethod() { + /* + some other comment that includes public class MyClass 2 { + */ return false; } - public static boolean methodWithComment() { // Behold the glory of this comment. Gaze upon it. + public static boolean someOtherMethod() { + public static String someString = 'this string has class MyClass2 { '; return true; } } diff --git a/src/lib/fixer.ts b/src/lib/fixer.ts index 5b25c0e..166c503 100644 --- a/src/lib/fixer.ts +++ b/src/lib/fixer.ts @@ -197,12 +197,45 @@ export class _PmdFixGenerator extends FixGenerator { // Split the text into lines for easier processing const lines = text.split('\n'); let classStartLine: number | undefined; + + const singleLineCommentPattern = /^\s*\/\//; + const blockCommentStartPattern = /^\s*\/\*/; + const blockCommentEndPattern = /\*\//; + // Regex matches class declaration not within double quotes + const classDeclarationPattern = /(?= 0; lineNumber--) { const line = lines[lineNumber]; - // if (line.includes(' class ')) { - if (line.match(/class\s+\w+/)) { + + // Check if we are in a block comment + if (inBlockComment) { + if (line.match(blockCommentStartPattern)) { + inBlockComment = false; + } + continue; + } + + // Skip single-line comments + if (line.match(singleLineCommentPattern)) { + continue; + } + + // Skip block comment in a single line + if (line.match(blockCommentEndPattern) && line.match(blockCommentStartPattern)) { + continue; + } + + // Check if this line is the start of a block comment + if (line.match(blockCommentEndPattern)) { + inBlockComment = true; + continue; + } + + const match = line.match(classDeclarationPattern); + if (!inBlockComment && match && !this.isWithinQuotes(line, match.index)) { classStartLine = lineNumber; break; } @@ -232,4 +265,21 @@ export class _PmdFixGenerator extends FixGenerator { // Return an empty string if no class declaration is found or it's the first line of the document return ''; } + + /** + * Helper function to check if match is within quotes + * @param line + * @param matchIndex + * @returns + */ + public isWithinQuotes(line: string, matchIndex: number): boolean { + const beforeMatch = line.slice(0, matchIndex); + const singleQuotesBefore = (beforeMatch.match(/'/g) || []).length; + const doubleQuotesBefore = (beforeMatch.match(/"/g) || []).length; + + // Check if the number of quotes before the match is odd (inside quotes) + if (singleQuotesBefore % 2 !== 0 || doubleQuotesBefore % 2 !== 0) return true; + + return false; + } } diff --git a/src/test/suite/fixer.test.ts b/src/test/suite/fixer.test.ts index 2101d8b..2725d30 100644 --- a/src/test/suite/fixer.test.ts +++ b/src/test/suite/fixer.test.ts @@ -133,7 +133,7 @@ suite('fixer.ts', () => { }); suite('Class-level suppression', () => { - test('Find class start position above the diagnostic line', async () => { + test('Should find class start position above the diagnostic line', async () => { const fileUri = vscode.Uri.file(path.join(codeFixturesPath, 'MyClass1.cls')); let doc: vscode.TextDocument; @@ -157,6 +157,82 @@ suite('fixer.ts', () => { expect(position.line).to.equal(6); expect(position.character).to.equal(0); }); + + test('Should ignore class defined in single line comment', async () => { + const fileUri = vscode.Uri.file(path.join(codeFixturesPath, 'MyClass2.cls')); + + let doc: vscode.TextDocument; + doc = await vscode.workspace.openTextDocument(fileUri); + const diag = new vscode.Diagnostic( + new vscode.Range( + new vscode.Position(10, 0), + new vscode.Position(10, 1) + ), + 'This message is unimportant', + vscode.DiagnosticSeverity.Warning + ); + + // Instantiate our fixer + const fixGenerator: _PmdFixGenerator = new _PmdFixGenerator(doc, diag); + + // Call findClassStartPosition method + const position = fixGenerator.findClassStartPosition(diag, doc); + + // Verify the position is correct + expect(position.line).to.equal(6); + expect(position.character).to.equal(0); + }); + + test('Should ignore class defined in a block comment comment', async () => { + const fileUri = vscode.Uri.file(path.join(codeFixturesPath, 'MyClass2.cls')); + + let doc: vscode.TextDocument; + doc = await vscode.workspace.openTextDocument(fileUri); + const diag = new vscode.Diagnostic( + new vscode.Range( + new vscode.Position(17, 0), + new vscode.Position(17, 1) + ), + 'This message is unimportant', + vscode.DiagnosticSeverity.Warning + ); + + // Instantiate our fixer + const fixGenerator: _PmdFixGenerator = new _PmdFixGenerator(doc, diag); + + // Call findClassStartPosition method + const position = fixGenerator.findClassStartPosition(diag, doc); + + // Verify the position is correct + expect(position.line).to.equal(6); + expect(position.character).to.equal(0); + }); + + test('Should ignore class defined as a string', async () => { + const fileUri = vscode.Uri.file(path.join(codeFixturesPath, 'MyClass2.cls')); + + let doc: vscode.TextDocument; + doc = await vscode.workspace.openTextDocument(fileUri); + const diag = new vscode.Diagnostic( + new vscode.Range( + new vscode.Position(23, 0), + new vscode.Position(23, 1) + ), + 'This message is unimportant', + vscode.DiagnosticSeverity.Warning + ); + + // Instantiate our fixer + const fixGenerator: _PmdFixGenerator = new _PmdFixGenerator(doc, diag); + + // Call findClassStartPosition method + const position = fixGenerator.findClassStartPosition(diag, doc); + + // Verify the position is correct + expect(position.line).to.equal(6); + expect(position.character).to.equal(0); + }); + }); }); }); From 3a5a424d2f56d7f8be0983021c20438580085df1 Mon Sep 17 00:00:00 2001 From: Jag Jayaprakash Date: Tue, 4 Jun 2024 15:44:31 -0700 Subject: [PATCH 5/9] NEW (Extension) @W-13991634@ Add class level PMD violations - part 5 --- src/lib/fixer.ts | 25 ++- src/test/suite/fixer.test.ts | 319 ++++++++++++++++++++++++----------- 2 files changed, 246 insertions(+), 98 deletions(-) diff --git a/src/lib/fixer.ts b/src/lib/fixer.ts index 166c503..cb57296 100644 --- a/src/lib/fixer.ts +++ b/src/lib/fixer.ts @@ -163,7 +163,7 @@ export class _PmdFixGenerator extends FixGenerator { if (!existingRules.includes(suppressionRule)) { // If the rule is not present, add it to the existing @SuppressWarnings const updatedRules = [...existingRules, suppressionRule].join(', '); - const updatedSuppression = `@SuppressWarnings('${updatedRules}')`; + const updatedSuppression = this.generateUpdatedSuppressionTag(updatedRules, this.document.languageId); const suppressionStartPosition = this.document.positionAt(classText.indexOf(suppressionMatch[0])); const suppressionEndPosition = this.document.positionAt(classText.indexOf(suppressionMatch[0]) + suppressionMatch[0].length); const suppressionRange = new vscode.Range(suppressionStartPosition, suppressionEndPosition); @@ -171,7 +171,7 @@ export class _PmdFixGenerator extends FixGenerator { } } else { // If @SuppressWarnings does not exist, insert a new one - const newSuppression = `@SuppressWarnings('${suppressionRule}')\n`; + const newSuppression = this.generateNewSuppressionTag(suppressionRule, this.document.languageId); action.edit.insert(this.document.uri, classStartPosition, newSuppression); } @@ -185,6 +185,24 @@ export class _PmdFixGenerator extends FixGenerator { return action; } + public generateUpdatedSuppressionTag(updatedRules: string, lang: string) { + if (lang === 'apex') { + return `@SuppressWarnings('${updatedRules}')`; + } else if (lang === 'java') { + return `@SuppressWarnings("${updatedRules}")`; + } + return ''; + } + + public generateNewSuppressionTag(suppressionRule: string, lang: string) { + if (lang === 'apex') { + return `@SuppressWarnings('${suppressionRule}')\n`; + } else if (lang === 'java') { + return `@SuppressWarnings("${suppressionRule}")\n`; + } + return ''; + } + /** * Finds the start position of the class in the document. * Assumes that the class declaration starts with the keyword "class". @@ -251,7 +269,6 @@ export class _PmdFixGenerator extends FixGenerator { /** * Finds the entire line that is one line above a class declaration statement. - * Assumes that the class declaration starts with the keyword "class". * @returns The text of the line that is one line above the class declaration. */ public findLineBeforeClassStartDeclaration(classStartPosition: vscode.Position, document: vscode.TextDocument): string { @@ -262,7 +279,7 @@ export class _PmdFixGenerator extends FixGenerator { return lineBeforeClass.text; } - // Return an empty string if no class declaration is found or it's the first line of the document + // Return an empty string if it's the first line of the document return ''; } diff --git a/src/test/suite/fixer.test.ts b/src/test/suite/fixer.test.ts index 2725d30..b4d8040 100644 --- a/src/test/suite/fixer.test.ts +++ b/src/test/suite/fixer.test.ts @@ -133,104 +133,235 @@ suite('fixer.ts', () => { }); suite('Class-level suppression', () => { - test('Should find class start position above the diagnostic line', async () => { - const fileUri = vscode.Uri.file(path.join(codeFixturesPath, 'MyClass1.cls')); - - let doc: vscode.TextDocument; - doc = await vscode.workspace.openTextDocument(fileUri); - const diag = new vscode.Diagnostic( - new vscode.Range( - new vscode.Position(7, 4), - new vscode.Position(7, 10) - ), - 'This message is unimportant', - vscode.DiagnosticSeverity.Warning - ); - - // Instantiate our fixer - const fixGenerator: _PmdFixGenerator = new _PmdFixGenerator(doc, diag); - - // Call findClassStartPosition method - const position = fixGenerator.findClassStartPosition(diag, doc); - - // Verify the position is correct - expect(position.line).to.equal(6); - expect(position.character).to.equal(0); + suite('#findClassStartPosition()', () => { + test('Should find class start position above the diagnostic line', async () => { + const fileUri = vscode.Uri.file(path.join(codeFixturesPath, 'MyClass1.cls')); + + let doc: vscode.TextDocument; + doc = await vscode.workspace.openTextDocument(fileUri); + const diag = new vscode.Diagnostic( + new vscode.Range( + new vscode.Position(7, 4), + new vscode.Position(7, 10) + ), + 'This message is unimportant', + vscode.DiagnosticSeverity.Warning + ); + + // Instantiate our fixer + const fixGenerator: _PmdFixGenerator = new _PmdFixGenerator(doc, diag); + + // Call findClassStartPosition method + const position = fixGenerator.findClassStartPosition(diag, doc); + + // Verify the position is correct + expect(position.line).to.equal(6); + expect(position.character).to.equal(0); + }); + + test('Should ignore class defined in single line comment', async () => { + const fileUri = vscode.Uri.file(path.join(codeFixturesPath, 'MyClass2.cls')); + + let doc: vscode.TextDocument; + doc = await vscode.workspace.openTextDocument(fileUri); + const diag = new vscode.Diagnostic( + new vscode.Range( + new vscode.Position(10, 0), + new vscode.Position(10, 1) + ), + 'This message is unimportant', + vscode.DiagnosticSeverity.Warning + ); + + // Instantiate our fixer + const fixGenerator: _PmdFixGenerator = new _PmdFixGenerator(doc, diag); + + // Call findClassStartPosition method + const position = fixGenerator.findClassStartPosition(diag, doc); + + // Verify the position is correct + expect(position.line).to.equal(6); + expect(position.character).to.equal(0); + }); + + test('Should ignore class defined in a block comment comment', async () => { + const fileUri = vscode.Uri.file(path.join(codeFixturesPath, 'MyClass2.cls')); + + let doc: vscode.TextDocument; + doc = await vscode.workspace.openTextDocument(fileUri); + const diag = new vscode.Diagnostic( + new vscode.Range( + new vscode.Position(17, 0), + new vscode.Position(17, 1) + ), + 'This message is unimportant', + vscode.DiagnosticSeverity.Warning + ); + + // Instantiate our fixer + const fixGenerator: _PmdFixGenerator = new _PmdFixGenerator(doc, diag); + + // Call findClassStartPosition method + const position = fixGenerator.findClassStartPosition(diag, doc); + + // Verify the position is correct + expect(position.line).to.equal(6); + expect(position.character).to.equal(0); + }); + + test('Should ignore class defined as a string', async () => { + const fileUri = vscode.Uri.file(path.join(codeFixturesPath, 'MyClass2.cls')); + + let doc: vscode.TextDocument; + doc = await vscode.workspace.openTextDocument(fileUri); + const diag = new vscode.Diagnostic( + new vscode.Range( + new vscode.Position(23, 0), + new vscode.Position(23, 1) + ), + 'This message is unimportant', + vscode.DiagnosticSeverity.Warning + ); + + // Instantiate our fixer + const fixGenerator: _PmdFixGenerator = new _PmdFixGenerator(doc, diag); + + // Call findClassStartPosition method + const position = fixGenerator.findClassStartPosition(diag, doc); + + // Verify the position is correct + expect(position.line).to.equal(6); + expect(position.character).to.equal(0); + }); }); - - test('Should ignore class defined in single line comment', async () => { - const fileUri = vscode.Uri.file(path.join(codeFixturesPath, 'MyClass2.cls')); - - let doc: vscode.TextDocument; - doc = await vscode.workspace.openTextDocument(fileUri); - const diag = new vscode.Diagnostic( - new vscode.Range( - new vscode.Position(10, 0), - new vscode.Position(10, 1) - ), - 'This message is unimportant', - vscode.DiagnosticSeverity.Warning - ); - - // Instantiate our fixer - const fixGenerator: _PmdFixGenerator = new _PmdFixGenerator(doc, diag); - - // Call findClassStartPosition method - const position = fixGenerator.findClassStartPosition(diag, doc); - - // Verify the position is correct - expect(position.line).to.equal(6); - expect(position.character).to.equal(0); + }); + suite('#generateNewSuppressionTag()', () => { + // Instantiate our fixer + const fixGenerator: _PmdFixGenerator = new _PmdFixGenerator(null, null); + + test('Should generate the correct suppression tag for Apex language', async () => { + const suppressionRule = 'rule1'; + const lang = 'apex'; + const expectedSuppressionTag = `@SuppressWarnings('${suppressionRule}')\n`; + expect(fixGenerator.generateNewSuppressionTag(suppressionRule, lang)).to.equal(expectedSuppressionTag); }); - - test('Should ignore class defined in a block comment comment', async () => { - const fileUri = vscode.Uri.file(path.join(codeFixturesPath, 'MyClass2.cls')); - - let doc: vscode.TextDocument; - doc = await vscode.workspace.openTextDocument(fileUri); - const diag = new vscode.Diagnostic( - new vscode.Range( - new vscode.Position(17, 0), - new vscode.Position(17, 1) - ), - 'This message is unimportant', - vscode.DiagnosticSeverity.Warning - ); - - // Instantiate our fixer - const fixGenerator: _PmdFixGenerator = new _PmdFixGenerator(doc, diag); - - // Call findClassStartPosition method - const position = fixGenerator.findClassStartPosition(diag, doc); - - // Verify the position is correct - expect(position.line).to.equal(6); - expect(position.character).to.equal(0); + + test('Should generate the correct suppression tag for Java language', async () => { + const suppressionRule = 'rule2'; + const lang = 'java'; + const expectedSuppressionTag = `@SuppressWarnings("${suppressionRule}")\n`; + expect(fixGenerator.generateNewSuppressionTag(suppressionRule, lang)).to.equal(expectedSuppressionTag); + }); + + test('Should return an empty string for unsupported languages', async () => { + const suppressionRule = 'rule3'; + const lang = 'python'; // Assuming python as an unsupported language + expect(fixGenerator.generateNewSuppressionTag(suppressionRule, lang)).to.equal(''); + }); + }); + suite('#generateUpdatedSuppressionTag()', () => { + // Instantiate our fixer + const fixGenerator: _PmdFixGenerator = new _PmdFixGenerator(null, null); + + test('Should generate the correct suppression tag for Apex language with single quotes', async () => { + const updatedRules = 'rule1'; + const lang = 'apex'; + const expectedSuppressionTag = `@SuppressWarnings('${updatedRules}')`; + expect(fixGenerator.generateUpdatedSuppressionTag(updatedRules, lang)).to.equal(expectedSuppressionTag); + }); + + test('Should generate the correct suppression tag for Java language with double quotes', async () => { + const updatedRules = 'rule2'; + const lang = 'java'; + const expectedSuppressionTag = `@SuppressWarnings("${updatedRules}")`; + expect(fixGenerator.generateUpdatedSuppressionTag(updatedRules, lang)).to.equal(expectedSuppressionTag); + }); + + test('Should return an empty string for unsupported languages', async () => { + const updatedRules = 'rule3'; + const lang = 'python'; // Assuming python as an unsupported language + expect(fixGenerator.generateUpdatedSuppressionTag(updatedRules, lang)).to.equal(''); + }); + }); + suite('#findLineBeforeClassStartDeclaration()', () => { + // Instantiate our fixer + const fixGenerator: _PmdFixGenerator = new _PmdFixGenerator(null, null); + + test('Should find the correct line before class start declaration when it is not the first line', async () => { + const classStartPosition = new vscode.Position(2, 0); + const document = { + lineAt: (lineNumber: number) => { + return { + // Simulating document content and for easy testing, line number starts at 0 + text: `This is line ${lineNumber}`, + }; + }, + } as vscode.TextDocument; + + // Call findLineBeforeClassStartDeclaration method + const line = fixGenerator.findLineBeforeClassStartDeclaration(classStartPosition, document); + + // Verify the line content is correct + expect(line).to.equal('This is line 1'); }); - test('Should ignore class defined as a string', async () => { - const fileUri = vscode.Uri.file(path.join(codeFixturesPath, 'MyClass2.cls')); - - let doc: vscode.TextDocument; - doc = await vscode.workspace.openTextDocument(fileUri); - const diag = new vscode.Diagnostic( - new vscode.Range( - new vscode.Position(23, 0), - new vscode.Position(23, 1) - ), - 'This message is unimportant', - vscode.DiagnosticSeverity.Warning - ); - - // Instantiate our fixer - const fixGenerator: _PmdFixGenerator = new _PmdFixGenerator(doc, diag); - - // Call findClassStartPosition method - const position = fixGenerator.findClassStartPosition(diag, doc); - - // Verify the position is correct - expect(position.line).to.equal(6); - expect(position.character).to.equal(0); + test('Should return empty string when class declaration is the first line', async () => { + const classStartPosition = new vscode.Position(0, 0); + const document = { + lineAt: (lineNumber: number) => { + return { + // Simulating document content and for easy testing, line number starts at 0 + text: `This is line ${lineNumber}`, + }; + }, + } as vscode.TextDocument; + + // Call findLineBeforeClassStartDeclaration method + const line = fixGenerator.findLineBeforeClassStartDeclaration(classStartPosition, document); + + // Verify the line content is correct + expect(line).to.equal(''); + }); + }); + suite('#isWithinQuotes()', () => { + // Instantiate our fixer + const fixGenerator: _PmdFixGenerator = new _PmdFixGenerator(null, null); + + test('Should return true if the match is within single quotes', async () => { + const line = "This is 'a matching string'"; + const matchIndex = 15; // Index where match occurs within the string + const isWithin = fixGenerator.isWithinQuotes(line, matchIndex); + expect(isWithin).to.equal(true); + }); + + test('Should return true if the match is within double quotes', async () => { + const line = 'This is "a matching string"'; + const matchIndex = 21; // Index where match occurs within the string + const isWithin = fixGenerator.isWithinQuotes(line, matchIndex); + expect(isWithin).to.equal(true); + }); + + test('Should return false if the match is not within quotes', async () => { + const line = 'This is a line without quotes'; + const matchIndex = 5; // Index where match occurs within the string + const isWithin = fixGenerator.isWithinQuotes(line, matchIndex); + expect(isWithin).to.equal(false); + }); + + test('Should return false if the match is at the start of a string within quotes', async () => { + const line = "'quotes' is at the start of a string"; + const matchIndex = 10; // Index where match occurs within the string and it is after the quotes + const isWithin = fixGenerator.isWithinQuotes(line, matchIndex); + expect(isWithin).to.equal(false); + }); + + test('Should return true if the match is at the start of a string but quotes is not closed', async () => { + // This is an extreme case where someone opens a quote and has class defined in it + // and the closure of the quote is not on the same line. + const line = "'quotes is at the start of a string"; + const matchIndex = 10; // Index where match occurs within the string and it is after the quotes + const isWithin = fixGenerator.isWithinQuotes(line, matchIndex); + expect(isWithin).to.equal(true); }); }); From 729dfb90476a4b93d2fe72d2f473e61bcee968e0 Mon Sep 17 00:00:00 2001 From: Jag Jayaprakash Date: Tue, 4 Jun 2024 17:48:46 -0700 Subject: [PATCH 6/9] NEW (Extension) @W-13991634@ Add class level PMD violations - part 6 --- src/lib/fixer.ts | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/lib/fixer.ts b/src/lib/fixer.ts index cb57296..8d3b9ae 100644 --- a/src/lib/fixer.ts +++ b/src/lib/fixer.ts @@ -90,6 +90,12 @@ export class _NoOpFixGenerator extends FixGenerator { * @private Must be exported for testing purposes, but shouldn't be used publicly, hence the leading underscore. */ export class _PmdFixGenerator extends FixGenerator { + private singleLineCommentPattern = /^\s*\/\//; + private blockCommentStartPattern = /^\s*\/\*/; + private blockCommentEndPattern = /\*\//; + private classDeclarationPattern = /\b(\w+\s+)+class\s+\w+/; + public suppressionRegex = /@SuppressWarnings\s*\(\s*'([^']*)'\s*\)/; + /** * Generate an array of fixes, if possible. * @returns @@ -154,8 +160,7 @@ export class _PmdFixGenerator extends FixGenerator { // Extract text from the start to end of the class declaration to search for existing suppressions const classText = this.findLineBeforeClassStartDeclaration(classStartPosition, this.document); - const suppressionRegex = /@SuppressWarnings\s*\(\s*'([^']*)'\s*\)/; - const suppressionMatch = classText.match(suppressionRegex); + const suppressionMatch = classText.match(this.suppressionRegex); if (suppressionMatch) { // If @SuppressWarnings exists, check if the rule is already present @@ -216,12 +221,6 @@ export class _PmdFixGenerator extends FixGenerator { const lines = text.split('\n'); let classStartLine: number | undefined; - const singleLineCommentPattern = /^\s*\/\//; - const blockCommentStartPattern = /^\s*\/\*/; - const blockCommentEndPattern = /\*\//; - // Regex matches class declaration not within double quotes - const classDeclarationPattern = /(? Date: Fri, 7 Jun 2024 09:32:40 -0700 Subject: [PATCH 7/9] fix failing test due to merge --- src/test/suite/fixer.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/suite/fixer.test.ts b/src/test/suite/fixer.test.ts index e34e0d8..0ff70ce 100644 --- a/src/test/suite/fixer.test.ts +++ b/src/test/suite/fixer.test.ts @@ -124,8 +124,8 @@ suite('fixer.ts', () => { // Attempt to generate fixes for the file. const fixes: vscode.CodeAction[] = fixGenerator.generateFixes(existingFixes); - // We expect to get no fix, since there is already a fix - expect(fixes).to.have.lengthOf(0, 'Wrong action count'); + // We expect to get one fix (class level suppression), no new line level suppression added since there is already a fix + expect(fixes).to.have.lengthOf(1, 'Wrong action count'); }); /* From b7a2ddbe7e0eac19992728dc5c7ab30f89e6c3eb Mon Sep 17 00:00:00 2001 From: Jag Jayaprakash Date: Mon, 10 Jun 2024 07:55:20 -0700 Subject: [PATCH 8/9] address code review comments --- code-fixtures/fixer-tests/MyClass2.cls | 4 ++ src/lib/fixer.ts | 39 +++++------ src/test/suite/fixer.test.ts | 97 ++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 22 deletions(-) diff --git a/code-fixtures/fixer-tests/MyClass2.cls b/code-fixtures/fixer-tests/MyClass2.cls index de59c91..0e6d03e 100644 --- a/code-fixtures/fixer-tests/MyClass2.cls +++ b/code-fixtures/fixer-tests/MyClass2.cls @@ -22,4 +22,8 @@ public class MyClass2 { public static String someString = 'this string has class MyClass2 { '; return true; } + + private class MyInnerClass { + // Some inner class + } } diff --git a/src/lib/fixer.ts b/src/lib/fixer.ts index 1efb284..9dc7dbb 100644 --- a/src/lib/fixer.ts +++ b/src/lib/fixer.ts @@ -92,11 +92,11 @@ export class _NoOpFixGenerator extends FixGenerator { * @private Must be exported for testing purposes, but shouldn't be used publicly, hence the leading underscore. */ export class _PmdFixGenerator extends FixGenerator { - private singleLineCommentPattern = /^\s*\/\//; - private blockCommentStartPattern = /^\s*\/\*/; - private blockCommentEndPattern = /\*\//; - private classDeclarationPattern = /\b(\w+\s+)+class\s+\w+/; - public suppressionRegex = /@SuppressWarnings\s*\(\s*'([^']*)'\s*\)/; + public singleLineCommentPattern = /^\s*\/\//; + public blockCommentStartPattern = /^\s*\/\*/; + public blockCommentEndPattern = /\*\//; + public classDeclarationPattern = /\b(\w+\s+)+class\s+\w+/; + public suppressionRegex = /@SuppressWarnings\s*\(\s*["']([^"']*)["']\s*\)/i; /** * Generate an array of fixes, if possible. @@ -153,14 +153,13 @@ export class _PmdFixGenerator extends FixGenerator { public generateClassLevelSuppression(): vscode.CodeAction { // Find the end-of-line position of the class declaration where the diagnostic is found. const classStartPosition = this.findClassStartPosition(this.diagnostic, this.document); - // const classEndOfLinePosition = 0; const action = new vscode.CodeAction(messages.fixer.supressOnClass, vscode.CodeActionKind.QuickFix); action.edit = new vscode.WorkspaceEdit(); // Determine the appropriate suppression rule based on the type of diagnostic.code let suppressionRule: string; - if (typeof this.diagnostic.code === 'object' && this.diagnostic.code !== null && 'value' in this.diagnostic.code) { + if (typeof this.diagnostic.code == 'object' && 'value' in this.diagnostic.code) { suppressionRule = `PMD.${this.diagnostic.code.value}`; } else { suppressionRule = `PMD`; @@ -232,14 +231,18 @@ export class _PmdFixGenerator extends FixGenerator { let inBlockComment = false; // Iterate from the diagnostic line upwards to find the class declaration - for (let lineNumber = diagnosticLine; lineNumber >= 0; lineNumber--) { + for (let lineNumber = 0; lineNumber <= diagnosticLine; lineNumber++) { const line = lines[lineNumber]; - // Check if we are in a block comment - if (inBlockComment) { - if (line.match(this.blockCommentStartPattern)) { - inBlockComment = false; - } + // Check if this line is the start of a block comment + if (!inBlockComment && line.match(this.blockCommentStartPattern)) { + inBlockComment = true; + continue; + } + + // Check if we are in the end of block comment + if (inBlockComment && line.match(this.blockCommentEndPattern)) { + inBlockComment = false; continue; } @@ -252,12 +255,6 @@ export class _PmdFixGenerator extends FixGenerator { if (line.match(this.blockCommentEndPattern) && line.match(this.blockCommentStartPattern)) { continue; } - - // Check if this line is the start of a block comment - if (line.match(this.blockCommentEndPattern)) { - inBlockComment = true; - continue; - } const match = line.match(this.classDeclarationPattern); if (!inBlockComment && match && !this.isWithinQuotes(line, match.index)) { @@ -302,8 +299,6 @@ export class _PmdFixGenerator extends FixGenerator { const doubleQuotesBefore = (beforeMatch.match(/"/g) || []).length; // Check if the number of quotes before the match is odd (inside quotes) - if (singleQuotesBefore % 2 !== 0 || doubleQuotesBefore % 2 !== 0) return true; - - return false; + return singleQuotesBefore % 2 !== 0 || doubleQuotesBefore % 2 !== 0 } } diff --git a/src/test/suite/fixer.test.ts b/src/test/suite/fixer.test.ts index 0ff70ce..0e22e42 100644 --- a/src/test/suite/fixer.test.ts +++ b/src/test/suite/fixer.test.ts @@ -253,6 +253,30 @@ suite('fixer.ts', () => { // Call findClassStartPosition method const position = fixGenerator.findClassStartPosition(diag, doc); + // Verify the position is correct + expect(position.line).to.equal(6); + expect(position.character).to.equal(0); + }); + test('Should ignore inner class', async () => { + const fileUri = vscode.Uri.file(path.join(codeFixturesPath, 'MyClass2.cls')); + + let doc: vscode.TextDocument; + doc = await vscode.workspace.openTextDocument(fileUri); + const diag = new vscode.Diagnostic( + new vscode.Range( + new vscode.Position(27, 0), + new vscode.Position(27, 1) + ), + 'This message is unimportant', + vscode.DiagnosticSeverity.Warning + ); + + // Instantiate our fixer + const fixGenerator: _PmdFixGenerator = new _PmdFixGenerator(doc, diag); + + // Call findClassStartPosition method + const position = fixGenerator.findClassStartPosition(diag, doc); + // Verify the position is correct expect(position.line).to.equal(6); expect(position.character).to.equal(0); @@ -389,7 +413,80 @@ suite('fixer.ts', () => { }); }); + }); }); + suite('Regex Pattern Tests', () => { + let fixGenerator: _PmdFixGenerator; + + setup(() => { + fixGenerator = new _PmdFixGenerator(null, null); + }); + + test('singleLineCommentPattern matches single-line comments', () => { + const pattern = fixGenerator.singleLineCommentPattern; + + // Matching cases + expect(pattern.test('// This is a single-line comment')).to.be.true; + + // Non-matching cases + expect(pattern.test('This is not a comment')).to.be.false; + expect(pattern.test('/* This is a block comment start */')).to.be.false; + }); + + test('blockCommentStartPattern matches block comment starts', () => { + const pattern = fixGenerator.blockCommentStartPattern; + + // Matching cases + expect(pattern.test('/* This is a block comment start')).to.be.true; + expect(pattern.test(' /* This is an indented block comment start')).to.be.true; + + // Non-matching cases + expect(pattern.test('This is not a comment')).to.be.false; + expect(pattern.test('// This is a single-line comment')).to.be.false; + expect(pattern.test('*/ This is a block comment end')).to.be.false; + }); + + test('blockCommentEndPattern matches block comment ends', () => { + const pattern = fixGenerator.blockCommentEndPattern; + + // Matching cases + expect(pattern.test('*/')).to.be.true; + expect(pattern.test(' */ This is an indented block comment end')).to.be.true; + + // Non-matching cases + expect(pattern.test('This is not a comment')).to.be.false; + expect(pattern.test('// This is a single-line comment')).to.be.false; + expect(pattern.test('/* This is a block comment start')).to.be.false; + }); + + test('classDeclarationPattern matches class declarations', () => { + const pattern = fixGenerator.classDeclarationPattern; + + // Matching cases + expect(pattern.test('public class MyClass')).to.be.true; + expect(pattern.test('final public class MyClass')).to.be.true; + expect(pattern.test(' private static class MyClass')).to.be.true; + + // Non-matching cases + expect(pattern.test('class="MyClass"')).to.be.false; // HTML-like attribute + expect(pattern.test('String myClass = "some value"')).to.be.false; + }); + + test('suppressionRegex matches @SuppressWarnings annotations', () => { + const pattern = fixGenerator.suppressionRegex; + + // Matching cases + expect(pattern.test("@SuppressWarnings('PMD.Rule')")).to.be.true; + expect(pattern.test("@suppresswarnings('pmd.rule')")).to.be.true; + expect(pattern.test("@suppresswarnings('PMD.Rule')")).to.be.true; + expect(pattern.test('@SuppressWarnings("PMD.Rule")')).to.be.true; + + // Non-matching cases + expect(pattern.test('This is not a suppression annotation')).to.be.false; + expect(pattern.test('@SuppressWarnings')).to.be.false; + expect(pattern.test('SuppressWarnings("PMD.Rule")')).to.be.false; // Missing '@' + }); + }); }); }); From 40f5faf659cdd94ceebc0e95ea70311288923ef5 Mon Sep 17 00:00:00 2001 From: Jag Jayaprakash Date: Mon, 10 Jun 2024 09:33:50 -0700 Subject: [PATCH 9/9] address code review comments - 2 --- code-fixtures/fixer-tests/MyClass2.cls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code-fixtures/fixer-tests/MyClass2.cls b/code-fixtures/fixer-tests/MyClass2.cls index 0e6d03e..3a9182c 100644 --- a/code-fixtures/fixer-tests/MyClass2.cls +++ b/code-fixtures/fixer-tests/MyClass2.cls @@ -19,7 +19,7 @@ public class MyClass2 { } public static boolean someOtherMethod() { - public static String someString = 'this string has class MyClass2 { '; + public static String someString = 'this string has \' class MyClass2 { '; return true; }