From d9f57a43832f7bbc5c0e3ac7c19d803b0a06222d Mon Sep 17 00:00:00 2001 From: Stephen Carter Date: Mon, 18 Mar 2024 16:21:33 -0400 Subject: [PATCH 01/10] Remove PMD6 alltogether and make PMD7-rc4 the default for now as wel wait for PMD7 ga --- messages/describe.md | 8 -- messages/list.md | 8 -- messages/run-pathless.md | 8 -- package.json | 4 +- pmd-cataloger/build.gradle.kts | 49 -------- src/Constants.ts | 2 - src/Controller.ts | 4 +- src/commands/scanner/rule/describe.ts | 5 - src/commands/scanner/rule/list.ts | 6 +- src/commands/scanner/run.ts | 6 +- src/lib/actions/AbstractRunAction.ts | 2 - src/lib/actions/RuleDescribeAction.ts | 2 - src/lib/actions/RuleListAction.ts | 2 - src/lib/pmd/PmdCommandInfo.ts | 33 +----- test/commands/scanner/run.filters.test.ts | 4 +- test/lib/actions/RuleDescribeAction.test.ts | 46 -------- test/lib/actions/RuleListAction.test.ts | 50 +-------- test/lib/actions/RunAction.pmd7.test.ts | 117 -------------------- test/lib/output/ResultsFormatting.test.ts | 23 +--- test/lib/pmd/PmdEngine.test.ts | 26 +---- 20 files changed, 15 insertions(+), 390 deletions(-) delete mode 100644 test/lib/actions/RunAction.pmd7.test.ts diff --git a/messages/describe.md b/messages/describe.md index 40b57f11e..5226678da 100644 --- a/messages/describe.md +++ b/messages/describe.md @@ -14,14 +14,6 @@ the name of the rule The name of the rule. -# flags.previewPmd7Summary - -use PMD version %s to describe PMD and CPD rules - -# flags.previewPmd7Description - -Uses PMD version %s instead of %s to describe PMD and CPD rules. - # output.noMatchingRules No rules were found with the name '%s'. diff --git a/messages/list.md b/messages/list.md index 6efab1a29..20b45c257 100644 --- a/messages/list.md +++ b/messages/list.md @@ -38,14 +38,6 @@ select rules by engine Selects rules by engine. Enter multiple engines as a comma-separated list. -# flags.previewPmd7Summary - -use PMD version %s to list PMD and CPD rules - -# flags.previewPmd7Description - -Uses PMD version %s instead of %s to list PMD and CPD rules. - # rulesetDeprecation The 'ruleset' command parameter is deprecated. Use 'category' instead diff --git a/messages/run-pathless.md b/messages/run-pathless.md index ce2da5bfc..20ae1e202 100644 --- a/messages/run-pathless.md +++ b/messages/run-pathless.md @@ -66,14 +66,6 @@ specify location of PMD rule reference XML file to customize rule selection Specifies the location of the PMD rule reference XML file to customize rule selection. -# flags.previewPmd7Summary - -use PMD version %s when running PMD and CPD - -# flags.previewPmd7Description - -Uses PMD version %s instead of %s when running PMD and CPD engines. - # flags.verboseViolationsSummary return retire-js violation message details diff --git a/package.json b/package.json index c209a16e3..322ef5f63 100644 --- a/package.json +++ b/package.json @@ -140,8 +140,8 @@ "prepack": "rm -rf lib && tsc -b && oclif manifest && oclif readme && oclif lock && npm shrinkwrap", "postpack": "rm -f oclif.manifest.json oclif.lock npm-shrinkwrap.json", "lint-typescript": "eslint ./src --ext .ts --max-warnings 0", - "test": "./gradlew test jacocoTestCoverageVerification && nyc mocha --timeout 10000 --retries 5 \"./test/**/*.test.ts\"", - "test-quiet": "cross-env SFGE_LOGGING=false ./gradlew test jacocoTestCoverageVerification && nyc mocha --timeout 10000 --retries 5 \"./test/**/*.test.ts\"", + "test": "./gradlew test jacocoTestCoverageVerification && nyc mocha --timeout 60000 --retries 5 \"./test/**/*.test.ts\"", + "test-quiet": "cross-env SFGE_LOGGING=false ./gradlew test jacocoTestCoverageVerification && nyc mocha --timeout 60000 --retries 5 \"./test/**/*.test.ts\"", "test-cli-messaging": "./gradlew cli-messaging:test cli-messaging:jacocoTestCoverageVerification", "test-pmd-cataloger": "./gradlew pmd-cataloger:test pmd-cataloger:jacocoTestCoverageVerification", "test-sfge": "./gradlew sfge:test sfge:jacocoTestCoverageVerification", diff --git a/pmd-cataloger/build.gradle.kts b/pmd-cataloger/build.gradle.kts index 79e64435c..ee919442f 100644 --- a/pmd-cataloger/build.gradle.kts +++ b/pmd-cataloger/build.gradle.kts @@ -60,53 +60,6 @@ tasks.register("deletePmdCatalogerDist") { delete(pmdCatalogerDistDir) } -// ======== DEFINE/UPDATE PMD6 DIST RELATED TASKS ===================================================================== -val pmd6DistDir = "$distDir/pmd" -val pmd6Version = "6.55.0" -val pmd6File = "pmd-bin-$pmd6Version.zip" - -tasks.register("downloadPmd6") { - src("https://github.com/pmd/pmd/releases/download/pmd_releases%2F${pmd6Version}/${pmd6File}") - dest(buildDir) - overwrite(false) -} - -tasks.register("installPmd6") { - dependsOn("downloadPmd6") - from(zipTree("$buildDir/$pmd6File")) - - // I went to https://github.com/pmd/pmd/tree/pmd_releases/6.55.0 and for each of the languages that we support - // (apex, java, visualforce, xml), I took a look at its direct and indirect dependencies at - // https://central.sonatype.com/artifact/net.sourceforge.pmd/pmd-apex/dependencies - // by selecting the 6.55.0 dropdown and clicking on "Dependencies" and selecting "All Dependencies". - // For completeness, I listed the modules and all their compile time dependencies (direct and indirect). - // Duplicates don't matter since we use setOf. - val pmd6ModulesToInclude = setOf( - // LANGUAGE MODULE DEPENDENCIES (direct and indirect) - "pmd-apex", "animal-sniffer-annotations", "antlr", "antlr-runtime", "antlr4-runtime", "aopalliance", "asm", "cglib", "commons-lang3", "error_prone_annotations", "gson", "j2objc-annotations", "javax.inject", "jcommander", "jol-core", "jsr305", "logback-classic", "logback-core", "pmd-apex-jorje", "pmd-core", "saxon", "slf4j-api", "stringtemplate", - "pmd-java", "antlr4-runtime", "asm", "commons-lang3", "gson", "jcommander", "pmd-core", "saxon", - "pmd-visualforce", "animal-sniffer-annotations", "antlr", "antlr-runtime", "antlr4-runtime", "aopalliance", "asm", "cglib", "commons-lang3", "error_prone_annotations", "gson", "j2objc-annotations", "javax.inject", "jcommander", "jol-core", "jsr305", "logback-classic", "logback-core", "pmd-apex", "pmd-apex-jorje", "pmd-core", "saxon", "slf4j-api", "stringtemplate", - "pmd-xml", "antr4-runtime", "asm", "commons-lang3", "gson", "jcommander", "pmd-core", "saxon" - ) - - val pmd6JarsToIncludeRegexes = mutableSetOf("""^LICENSE""".toRegex()) - pmd6ModulesToInclude.forEach { - pmd6JarsToIncludeRegexes.add("""^$it-.*\.jar""".toRegex()) - } - - include { details: FileTreeElement -> pmd6JarsToIncludeRegexes.any { it.containsMatchIn(details.file.name) } } - into(pmd6DistDir) - includeEmptyDirs = false - eachFile { - // We drop the parent "pmd-bin-6.55.0" folder and put files directly into our "pmd" folder - relativePath = RelativePath(true, *relativePath.segments.drop(1).toTypedArray()) - } -} - -tasks.register("deletePmd6Dist") { - delete(pmd6DistDir) -} - // ======== DEFINE/UPDATE PMD7 DIST RELATED TASKS ===================================================================== val pmd7DistDir = "$distDir/pmd7" @@ -160,13 +113,11 @@ tasks.register("deletePmd7Dist") { // ======== ATTACH TASKS TO ASSEMBLE AND CLEAN ======================================================================== tasks.assemble { dependsOn("installDist") - dependsOn("installPmd6") dependsOn("installPmd7") } tasks.clean { dependsOn("deletePmdCatalogerDist") - dependsOn("deletePmd6Dist") dependsOn("deletePmd7Dist") } diff --git a/src/Constants.ts b/src/Constants.ts index d0f86b934..2495a98e4 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -1,7 +1,6 @@ import os = require('os'); import path = require('path'); -export const PMD6_VERSION = '6.55.0'; export const PMD7_VERSION = '7.0.0-rc4'; export const PMD_APPEXCHANGE_RULES_VERSION = '0.12'; export const SFGE_VERSION = '1.0.1-pilot'; @@ -133,7 +132,6 @@ export enum Severity { } // Here, current dir __dirname = /sfdx-scanner/src -export const PMD6_LIB = path.join(__dirname, '..', 'dist', 'pmd', 'lib'); export const PMD7_LIB = path.join(__dirname, '..', 'dist', 'pmd7', 'lib'); // Here, current dir __dirname = /sfdx-scanner/src diff --git a/src/Controller.ts b/src/Controller.ts index de0637a0f..27125ca13 100644 --- a/src/Controller.ts +++ b/src/Controller.ts @@ -9,7 +9,7 @@ import {RuleEngine} from './lib/services/RuleEngine'; import {RulePathManager} from './lib/RulePathManager'; import {RuleCatalog} from './lib/services/RuleCatalog'; import {BundleName, getMessage} from "./MessageCatalog"; -import {Pmd6CommandInfo, PmdCommandInfo} from "./lib/pmd/PmdCommandInfo"; +import {Pmd7CommandInfo, PmdCommandInfo} from "./lib/pmd/PmdCommandInfo"; /** * Converts an array of RuleEngines to a sorted, comma delimited * string of their names. @@ -30,7 +30,7 @@ declare global { // eslint-disable-next-line no-var var _activePmdCommandInfo: PmdCommandInfo; } -globalThis._activePmdCommandInfo = new Pmd6CommandInfo(); +globalThis._activePmdCommandInfo = new Pmd7CommandInfo(); // This is probably more appropriately called a ProviderFactory (Salesforce Core folks know this code smell all too well) export const Controller = { diff --git a/src/commands/scanner/rule/describe.ts b/src/commands/scanner/rule/describe.ts index 199753242..c668c91a7 100644 --- a/src/commands/scanner/rule/describe.ts +++ b/src/commands/scanner/rule/describe.ts @@ -5,7 +5,6 @@ import {BundleName, getMessage} from "../../../MessageCatalog"; import {Logger} from "@salesforce/core"; import {Display} from "../../../lib/Display"; import {RuleDescribeAction} from "../../../lib/actions/RuleDescribeAction"; -import {PMD6_VERSION, PMD7_VERSION} from "../../../Constants"; /** * Defines the "rule describe" command for the "scanner" cli. @@ -31,10 +30,6 @@ export default class Describe extends ScannerCommand { summary: getMessage(BundleName.Common, 'flags.verboseSummary'), description: getMessage(BundleName.Common, 'flags.verboseDescription') }), - "preview-pmd7": Flags.boolean({ - summary: getMessage(BundleName.Describe, 'flags.previewPmd7Summary', [PMD7_VERSION]), - description: getMessage(BundleName.Describe, 'flags.previewPmd7Description', [PMD7_VERSION, PMD6_VERSION]) - }), }; protected createAction(_logger: Logger, display: Display): Action { diff --git a/src/commands/scanner/rule/list.ts b/src/commands/scanner/rule/list.ts index 1537f6eff..a51150734 100644 --- a/src/commands/scanner/rule/list.ts +++ b/src/commands/scanner/rule/list.ts @@ -1,6 +1,6 @@ import {Flags} from '@salesforce/sf-plugins-core'; import {Action, ScannerCommand} from '../../../lib/ScannerCommand'; -import {AllowedEngineFilters, PMD6_VERSION, PMD7_VERSION} from '../../../Constants'; +import {AllowedEngineFilters} from '../../../Constants'; import {BundleName, getMessage} from "../../../MessageCatalog"; import {Logger} from "@salesforce/core"; import {Display} from "../../../lib/Display"; @@ -58,10 +58,6 @@ export default class List extends ScannerCommand { delimiter: ',', multiple: true })(), - "preview-pmd7": Flags.boolean({ - summary: getMessage(BundleName.List, 'flags.previewPmd7Summary', [PMD7_VERSION]), - description: getMessage(BundleName.List, 'flags.previewPmd7Description', [PMD7_VERSION, PMD6_VERSION]) - }), }; protected createAction(_logger: Logger, display: Display): Action { diff --git a/src/commands/scanner/run.ts b/src/commands/scanner/run.ts index 1a0c64ebf..691a33ebb 100644 --- a/src/commands/scanner/run.ts +++ b/src/commands/scanner/run.ts @@ -1,5 +1,5 @@ import {Flags} from '@salesforce/sf-plugins-core'; -import {PathlessEngineFilters, PMD6_VERSION, PMD7_VERSION} from '../../Constants'; +import {PathlessEngineFilters} from '../../Constants'; import {ScannerRunCommand} from '../../lib/ScannerRunCommand'; import {EngineOptionsFactory, RunEngineOptionsFactory} from "../../lib/EngineOptionsFactory"; import {InputProcessor, InputProcessorImpl} from "../../lib/InputProcessor"; @@ -70,10 +70,6 @@ export default class Run extends ScannerRunCommand { summary: getMessage(BundleName.Run, 'flags.pmdConfigSummary'), description: getMessage(BundleName.Run, 'flags.pmdConfigDescription') }), - "preview-pmd7": Flags.boolean({ - summary: getMessage(BundleName.Run, 'flags.previewPmd7Summary', [PMD7_VERSION]), - description: getMessage(BundleName.Run, 'flags.previewPmd7Description', [PMD7_VERSION, PMD6_VERSION]) - }), // TODO: This flag was implemented for W-7791882, and it's suboptimal. It leaks the abstraction and pollutes the command. // It should be replaced during the 3.0 release cycle. diff --git a/src/lib/actions/AbstractRunAction.ts b/src/lib/actions/AbstractRunAction.ts index 43eb50949..b918cc3d2 100644 --- a/src/lib/actions/AbstractRunAction.ts +++ b/src/lib/actions/AbstractRunAction.ts @@ -21,7 +21,6 @@ import {ResultsProcessorFactory} from "../output/ResultsProcessorFactory"; import {JsonReturnValueHolder} from "../output/JsonReturnValueHolder"; import untildify = require('untildify'); import normalize = require('normalize-path'); -import {Pmd6CommandInfo, Pmd7CommandInfo} from "../pmd/PmdCommandInfo"; /** * Abstract Action to share a common implementation behind the "run" and "run dfa" commands @@ -81,7 +80,6 @@ export abstract class AbstractRunAction implements Action { } async run(inputs: Inputs): Promise { - Controller.setActivePmdCommandInfo(inputs['preview-pmd7'] ? new Pmd7CommandInfo() : new Pmd6CommandInfo()); const filters: RuleFilter[] = this.ruleFilterFactory.createRuleFilters(inputs); const targetPaths: string[] = this.inputProcessor.resolveTargetPaths(inputs); const runOptions: RunOptions = this.inputProcessor.createRunOptions(inputs, this.isDfa()); diff --git a/src/lib/actions/RuleDescribeAction.ts b/src/lib/actions/RuleDescribeAction.ts index 5f9e7388d..af5e32de7 100644 --- a/src/lib/actions/RuleDescribeAction.ts +++ b/src/lib/actions/RuleDescribeAction.ts @@ -9,7 +9,6 @@ import Dfa from "../../commands/scanner/run/dfa"; import Run from "../../commands/scanner/run"; import {Display} from "../Display"; import {RuleFilterFactory} from "../RuleFilterFactory"; -import {Pmd6CommandInfo, Pmd7CommandInfo} from "../pmd/PmdCommandInfo"; type DescribeStyledRule = Rule & { runWith: string; @@ -34,7 +33,6 @@ export class RuleDescribeAction implements Action { } public async run(inputs: Inputs): Promise { - Controller.setActivePmdCommandInfo(inputs['preview-pmd7'] ? new Pmd7CommandInfo() : new Pmd6CommandInfo()); const ruleFilters: RuleFilter[] = this.ruleFilterFactory.createRuleFilters(inputs); // TODO: Inject RuleManager as a dependency to improve testability by removing coupling to runtime implementation diff --git a/src/lib/actions/RuleListAction.ts b/src/lib/actions/RuleListAction.ts index f0c5f2521..22663d54d 100644 --- a/src/lib/actions/RuleListAction.ts +++ b/src/lib/actions/RuleListAction.ts @@ -7,7 +7,6 @@ import {RuleFilter} from "../RuleFilter"; import {Controller} from "../../Controller"; import {Display} from "../Display"; import {RuleFilterFactory} from "../RuleFilterFactory"; -import {Pmd6CommandInfo, Pmd7CommandInfo} from "../pmd/PmdCommandInfo"; const MSG_YES: string = getMessage(BundleName.List, 'yes'); const MSG_NO: string = getMessage(BundleName.List, 'no'); @@ -60,7 +59,6 @@ export class RuleListAction implements Action { } public async run(inputs: Inputs): Promise { - Controller.setActivePmdCommandInfo(inputs['preview-pmd7'] ? new Pmd7CommandInfo() : new Pmd6CommandInfo()); const ruleFilters: RuleFilter[] = this.ruleFilterFactory.createRuleFilters(inputs); // TODO: Inject RuleManager as a dependency to improve testability by removing coupling to runtime implementation diff --git a/src/lib/pmd/PmdCommandInfo.ts b/src/lib/pmd/PmdCommandInfo.ts index a5aaa0610..15d6c1466 100644 --- a/src/lib/pmd/PmdCommandInfo.ts +++ b/src/lib/pmd/PmdCommandInfo.ts @@ -1,8 +1,6 @@ -import {PMD6_LIB, PMD6_VERSION, PMD7_LIB, PMD7_VERSION} from "../../Constants"; +import {PMD7_LIB, PMD7_VERSION} from "../../Constants"; import * as path from 'path'; -const PMD6_MAIN_CLASS = 'net.sourceforge.pmd.PMD'; -const CPD6_MAIN_CLASS = 'net.sourceforge.pmd.cpd.CPD'; const PMD7_CLI_CLASS = 'net.sourceforge.pmd.cli.PmdCli'; const HEAP_SIZE = '-Xmx1024m'; @@ -16,35 +14,6 @@ export interface PmdCommandInfo { constructJavaCommandArgsForCpd(fileList: string, minimumTokens: number, language: string): string[] } -export class Pmd6CommandInfo implements PmdCommandInfo { - getVersion(): string { - return PMD6_VERSION; - } - - getJarPathForLanguage(language: string): string { - return path.join(PMD6_LIB, `pmd-${language}-${this.getVersion()}.jar`); - } - - constructJavaCommandArgsForPmd(fileList: string, classPathsForExternalRules: string[], rulesets: string): string[] { - // The classpath needs PMD's lib folder. There may be redundancy with the shared classpath, but having the - // same JAR in the classpath twice is fine. Also note that the classpath is not wrapped in quotes like how it - // would be if we invoked directly through the CLI, because child_process.spawn() hates that. - const classpath = classPathsForExternalRules.concat([`${PMD6_LIB}/*`]).join(path.delimiter); - const args = ['-cp', classpath, HEAP_SIZE, PMD6_MAIN_CLASS, '-filelist', fileList, - '-format', 'xml']; - if (rulesets.length > 0) { - args.push('-rulesets', rulesets); - } - return args; - } - - constructJavaCommandArgsForCpd(fileList: string, minimumTokens: number, language: string): string[] { - const classpath = `${PMD6_LIB}/*`; - return ['-cp', classpath, HEAP_SIZE, CPD6_MAIN_CLASS, '--filelist', fileList, - '--format', 'xml', '--minimum-tokens', minimumTokens.toString(), '--language', language]; - } -} - export class Pmd7CommandInfo implements PmdCommandInfo { getVersion(): string { return PMD7_VERSION; diff --git a/test/commands/scanner/run.filters.test.ts b/test/commands/scanner/run.filters.test.ts index debac5709..820015565 100644 --- a/test/commands/scanner/run.filters.test.ts +++ b/test/commands/scanner/run.filters.test.ts @@ -45,7 +45,9 @@ describe('scanner run tests that result in the use of RuleFilters', function () expect(csv.indexOf('\n')).to.equal(-1, "Should be no violations detected"); }); - it('Case: --engine pmd-appexchange against unclean code', () => { + // Currently this test fails because the pmd-appexchange jar files depend on classes that only exist in PMD6 + // TODO: Turn this test back on as soon as we get the new jar files for pmd-appexchange that work with PMD7 + xit('Case: --engine pmd-appexchange against unclean code', () => { const output = runCommand(`scanner run --target ${path.join('test', 'code-fixtures', 'projects', 'pmd-appexchange-test-app', 'objects', 'unclean.object')} --format json --engine pmd-appexchange`); const stdout = output.shellOutput.stdout; const results = JSON.parse(stdout.slice(stdout.indexOf('['), stdout.lastIndexOf(']') + 1)); diff --git a/test/lib/actions/RuleDescribeAction.test.ts b/test/lib/actions/RuleDescribeAction.test.ts index d36576ef1..791027ae7 100644 --- a/test/lib/actions/RuleDescribeAction.test.ts +++ b/test/lib/actions/RuleDescribeAction.test.ts @@ -7,8 +7,6 @@ import {initializeTestSetup} from "../../test-related-lib/TestOverrides"; import {RuleFilterFactoryImpl} from "../../../src/lib/RuleFilterFactory"; import {Inputs} from "../../../src/types"; import {RuleDescribeAction} from "../../../src/lib/actions/RuleDescribeAction"; -import {Pmd6CommandInfo} from "../../../src/lib/pmd/PmdCommandInfo"; -import {Controller} from "../../../src/Controller"; import {getMessage, BundleName} from '../../../src/MessageCatalog'; describe("RuleDescribeAction", () => { @@ -95,49 +93,5 @@ describe("RuleDescribeAction", () => { expect(displayedRule['description']).to.equal('Require `super()` calls in constructors', 'Wrong description in displayed output'); }); }); - - describe('When PMD7 binary is invoked...', () => { - afterEach(() => { - // Until we remove global state, we should clean up after ourselves to prevent other tests from being impacted - Controller.setActivePmdCommandInfo(new Pmd6CommandInfo()); - }) - - it('PMD7 pmd rule is returned', async () => { - const inputs: Inputs = { - rulename: 'ApexCRUDViolation', - "preview-pmd7": true - } - await ruleDescribeAction.run(inputs); - - const rule: AnyJson = display.getLastStyledObject(); - expect(rule['name']).to.equal('ApexCRUDViolation'); - expect(rule['engine']).to.equal('pmd'); - expect(rule['isPilot']).to.equal(false); - expect(rule['enabled']).to.equal(true); - expect(rule['categories']).to.deep.equal(['Security']); - expect(rule['rulesets']).to.contain('quickstart'); - expect(rule['languages']).to.deep.equal(['apex']); - expect(rule['description']).to.have.length.greaterThan(0); - expect(rule['message']).to.have.length.greaterThan(0); - }); - - it('PMD7 cpd rule is returned', async () => { - const inputs: Inputs = { - rulename: 'copy-paste-detected', - "preview-pmd7": true - }; - await ruleDescribeAction.run(inputs); - - const rule: AnyJson = display.getLastStyledObject(); - expect(rule['name']).to.equal('copy-paste-detected'); - expect(rule['engine']).to.equal('cpd'); - expect(rule['isPilot']).to.equal(false); - expect(rule['enabled']).to.equal(false); - expect(rule['categories']).to.deep.equal(['Copy/Paste Detected']); - expect(rule['rulesets']).to.deep.equal([]); - expect(rule['languages']).to.deep.equal(['apex', 'java', 'visualforce', 'xml']); - expect(rule['description']).to.have.length.greaterThan(0); - }); - }); }); }); diff --git a/test/lib/actions/RuleListAction.test.ts b/test/lib/actions/RuleListAction.test.ts index 99f4238fb..16e1bc4d3 100644 --- a/test/lib/actions/RuleListAction.test.ts +++ b/test/lib/actions/RuleListAction.test.ts @@ -8,9 +8,7 @@ import {initializeTestSetup} from "../../test-related-lib/TestOverrides"; import {RuleFilterFactoryImpl} from "../../../src/lib/RuleFilterFactory"; import {RuleListAction} from "../../../src/lib/actions/RuleListAction"; import {Inputs} from "../../../src/types"; -import {ENGINE, PMD7_LIB} from "../../../src/Constants"; -import {Controller} from "../../../src/Controller"; -import {Pmd6CommandInfo} from "../../../src/lib/pmd/PmdCommandInfo"; +import {ENGINE} from "../../../src/Constants"; import {Config} from '../../../src/lib/util/Config'; describe("Tests for RuleListAction", () => { @@ -98,50 +96,4 @@ describe("Tests for RuleListAction", () => { expect(tableData).to.have.length(0, 'No rules should have been logged'); }); }); - - describe("Tests to confirm that PMD7 binary files are invoked when choosing PMD7", () => { - afterEach(() => { - // Until we remove global state, we should cleanup after ourselves to prevent other tests from being impacted - Controller.setActivePmdCommandInfo(new Pmd6CommandInfo()) - }) - - it("When using PMD7, the rule list for the pmd engine should give back rules for PMD 7", async () => { - const inputs: Inputs = { - engine: ['pmd'], - "preview-pmd7": true - } - await ruleListAction.run(inputs); - - let tableData: Ux.Table.Data[] = display.getLastTableData(); - expect(tableData).to.have.length(67); - for (const rowData of tableData) { - expect(rowData.engine).to.equal("pmd"); - expect(rowData.sourcepackage).to.contain(PMD7_LIB); - expect(rowData.name).to.have.length.greaterThan(0); - expect(rowData.categories).to.have.length.greaterThan(0); - expect(rowData.isDfa).to.equal(false); - expect(rowData.isPilot).to.equal(false); - expect(rowData.languages).to.have.length.greaterThan(0); - } - }) - - it("When using PMD7, the rule list for the cpd engine should give back the copy-paste-detected rule", async () => { - const inputs: Inputs = { - engine: ['cpd'], - "preview-pmd7": true - } - await ruleListAction.run(inputs); - - let tableData: Ux.Table.Data[] = display.getLastTableData(); - expect(tableData).to.have.length(1); - expect(tableData[0].engine).to.equal("cpd"); - expect(tableData[0].sourcepackage).to.equal("cpd"); - expect(tableData[0].name).to.equal("copy-paste-detected"); - expect(tableData[0].categories).to.deep.equal(["Copy/Paste Detected"]); - expect(tableData[0].rulesets).to.deep.equal([]); - expect(tableData[0].isDfa).to.equal(false); - expect(tableData[0].isPilot).to.equal(false); - expect(tableData[0].languages).to.deep.equal(['apex', 'java', 'visualforce', 'xml']); - }); - }); }); diff --git a/test/lib/actions/RunAction.pmd7.test.ts b/test/lib/actions/RunAction.pmd7.test.ts deleted file mode 100644 index 880afad88..000000000 --- a/test/lib/actions/RunAction.pmd7.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -import {InputProcessor, InputProcessorImpl} from "../../../src/lib/InputProcessor"; -import {RuleFilterFactoryImpl} from "../../../src/lib/RuleFilterFactory"; -import {RunEngineOptionsFactory} from "../../../src/lib/EngineOptionsFactory"; -import {RunAction} from "../../../src/lib/actions/RunAction"; -import {FakeDisplay} from "../FakeDisplay"; -import {Logger} from "@salesforce/core"; -import {Inputs, PathlessRuleViolation, RuleResult} from "../../../src/types"; -import * as path from "path"; -import {initializeTestSetup} from "../../test-related-lib/TestOverrides"; -import {expect} from "chai"; -import {Results} from "../../../src/lib/output/Results"; -import {PMD6_VERSION, PMD7_VERSION} from "../../../src/Constants"; -import {FakeResultsProcessorFactory, RawResultsProcessor} from "./fakes"; -import {Controller} from "../../../src/Controller"; -import {Pmd6CommandInfo} from "../../../src/lib/pmd/PmdCommandInfo"; - -const codeFixturesDir = path.join(__dirname, '..', '..', 'code-fixtures'); -const pathToSomeTestClass = path.join(codeFixturesDir, 'apex', 'SomeTestClass.cls'); -const pathToCodeForCpd = path.join(codeFixturesDir, 'cpd'); - -describe("Tests for RunAction", () => { - let display: FakeDisplay; - let resultsProcessor: RawResultsProcessor; - let runAction: RunAction; - beforeEach(() => { - initializeTestSetup(); - display = new FakeDisplay(); - resultsProcessor = new RawResultsProcessor(); - - const inputProcessor: InputProcessor = new InputProcessorImpl("2.11.8", display); - runAction = new RunAction( - Logger.childFromRoot("forTesting"), - display, - inputProcessor, - new RuleFilterFactoryImpl(), - new RunEngineOptionsFactory(inputProcessor), - new FakeResultsProcessorFactory(resultsProcessor)); - }); - - describe("Tests to confirm that PMD7 binary files are invoked when choosing PMD7", () => { - afterEach(() => { - // Until we remove global state, we should cleanup after ourselves to prevent other tests from being impacted - Controller.setActivePmdCommandInfo(new Pmd6CommandInfo()) - }) - - it("When using PMD7, the pmd engine actually uses PMD7 instead of PMD6", async () => { - const inputs: Inputs = { - target: [pathToSomeTestClass], - engine: ['pmd'], - 'normalize-severity': true, - "preview-pmd7": true - } - await runAction.run(inputs); - - const results: Results = resultsProcessor.getResults(); - expect(results.getExecutedEngines().size).to.equal(1); - expect(results.getExecutedEngines()).to.contain('pmd'); - const ruleResults: RuleResult[] = results.getRuleResults(); - expect(ruleResults).to.have.length(1); - expect(ruleResults[0].violations).to.have.length(8); - for (let violation of ruleResults[0].violations) { - violation = violation as PathlessRuleViolation; - - // Unfortunately, there isn't an easy way to detect that we are using PMD 7 binaries other than checking - // that the violation urls contain version 7 information instead of version 6. - expect(violation.url).to.contain(PMD7_VERSION); - expect(violation.url).not.to.contain(PMD6_VERSION); - - // Other sanity checks to make the fields are filled in - expect(violation.ruleName).to.have.length.greaterThan(0); - expect(violation.category).to.have.length.greaterThan(0); - expect(violation.line).to.be.greaterThan(0); - expect(violation.column).to.be.greaterThan(0); - expect(violation.message).to.have.length.greaterThan(0); - expect(violation.severity).to.be.greaterThanOrEqual(3); - expect(violation.normalizedSeverity).to.equal(3); - } - }); - - it("When using PMD7, the cpd engine actually uses PMD7 instead of PMD6", async () => { - const inputs: Inputs = { - target: [pathToCodeForCpd], - engine: ['cpd'], - 'normalize-severity': true, - "preview-pmd7": true - } - await runAction.run(inputs); - - const results: Results = resultsProcessor.getResults(); - expect(results.getExecutedEngines().size).to.equal(1); - expect(results.getExecutedEngines()).to.contain('cpd'); - const ruleResults: RuleResult[] = results.getRuleResults(); - expect(ruleResults).to.have.length(2); - expect(ruleResults[0].violations).to.have.length(1); - expect(ruleResults[1].violations).to.have.length(1); - const violation1: PathlessRuleViolation = ruleResults[0].violations[0] as PathlessRuleViolation; - const violation2: PathlessRuleViolation = ruleResults[1].violations[0] as PathlessRuleViolation; - - for (let violation of [violation1, violation2]) { - - // Unfortunately, there isn't an easy way to detect that we are using PMD 7 binaries. - // The best we can do is check for 'latest' in the url. - expect(violation.url).to.contain('latest'); - expect(violation.url).not.to.contain(PMD6_VERSION); - - // Other sanity checks to make the fields are filled in - expect(violation.ruleName).to.have.length.greaterThan(0); - expect(violation.category).to.have.length.greaterThan(0); - expect(violation.line).to.be.greaterThan(0); - expect(violation.column).to.be.greaterThan(0); - expect(violation.message).to.have.length.greaterThan(0); - expect(violation.severity).to.be.greaterThanOrEqual(3); - expect(violation.normalizedSeverity).to.equal(3); - } - }); - }); -}); diff --git a/test/lib/output/ResultsFormatting.test.ts b/test/lib/output/ResultsFormatting.test.ts index eb85cba37..68c0fe32c 100644 --- a/test/lib/output/ResultsFormatting.test.ts +++ b/test/lib/output/ResultsFormatting.test.ts @@ -6,13 +6,10 @@ import path = require('path'); import * as csvParse from 'csv-parse'; import {parseString} from 'xml2js'; import * as TestOverrides from '../../test-related-lib/TestOverrides'; -import { PathlessEngineFilters, ENGINE, PMD6_VERSION, SFGE_VERSION } from '../../../src/Constants'; +import { PathlessEngineFilters, ENGINE, PMD7_VERSION, SFGE_VERSION } from '../../../src/Constants'; import { fail } from 'assert'; import {Results, RunResults} from "../../../src/lib/output/Results"; import { OutputFormat } from '../../../src/lib/output/OutputFormat'; -import {Controller} from "../../../src/Controller"; -import {Pmd7CommandInfo, PmdCommandInfo} from "../../../src/lib/pmd/PmdCommandInfo"; -import {PMD7_VERSION} from "../../../src/Constants"; const sampleFile1 = path.join('Users', 'SomeUser', 'samples', 'sample-file1.js'); const sampleFile2 = path.join('Users', 'SomeUser', 'samples', 'sample-file2.js'); @@ -536,7 +533,7 @@ describe('Results Formatting', () => { function validatePMDSarif(run: unknown, normalizeSeverity: boolean): void { const driver = run['tool']['driver']; expect(driver.name).to.equal('pmd'); - expect(driver.version).to.equal(PMD6_VERSION); + expect(driver.version).to.equal(PMD7_VERSION); expect(driver.informationUri).to.equal('https://pmd.github.io/pmd'); // tool.driver.rules @@ -790,22 +787,6 @@ describe('Results Formatting', () => { } } }); - - it ('Switching to PMD7 is reflected in sarif output for pmd and cpd engines', async () => { - const originalPmdCommandInfo: PmdCommandInfo = Controller.getActivePmdCommandInfo() - try { - Controller.setActivePmdCommandInfo(new Pmd7CommandInfo()); - for (const engine of ['pmd', 'cpd']) { - const results: Results = new RunResults(createSampleRuleResultsForEngine(engine), new Set([engine])); - const formattedOutput: FormattedOutput = await results.toFormattedOutput(OutputFormat.SARIF, false); - const sarifResults: unknown[] = JSON.parse(formattedOutput as string); - expect(sarifResults['runs']).to.have.lengthOf(1); - expect(sarifResults['runs'][0]['tool']['driver']['version']).to.equal(PMD7_VERSION); - } - } finally { - Controller.setActivePmdCommandInfo(originalPmdCommandInfo); - } - }) }); describe('Output Format: JSON', () => { diff --git a/test/lib/pmd/PmdEngine.test.ts b/test/lib/pmd/PmdEngine.test.ts index f9e03a81b..a600d5ce5 100644 --- a/test/lib/pmd/PmdEngine.test.ts +++ b/test/lib/pmd/PmdEngine.test.ts @@ -7,14 +7,12 @@ import Sinon = require('sinon'); import {PmdEngine, _PmdRuleMapper} from '../../../src/lib/pmd/PmdEngine' import {uxEvents, EVENTS} from '../../../src/lib/ScannerEvents'; import * as TestOverrides from '../../test-related-lib/TestOverrides'; -import {CUSTOM_CONFIG, ENGINE, LANGUAGE, PMD6_VERSION, PMD7_LIB, PMD7_VERSION} from '../../../src/Constants'; +import {CUSTOM_CONFIG, ENGINE, LANGUAGE, PMD7_LIB, PMD7_VERSION} from '../../../src/Constants'; import * as DataGenerator from '../eslint/EslintTestDataGenerator'; import {BundleName, getMessage} from "../../../src/MessageCatalog"; import {Config} from "../../../src/lib/util/Config"; import {CustomRulePathManager} from "../../../src/lib/CustomRulePathManager"; import {after} from "mocha"; -import {Pmd7CommandInfo, PmdCommandInfo} from "../../../src/lib/pmd/PmdCommandInfo"; -import {Controller} from "../../../src/Controller"; TestOverrides.initializeTestSetup(); @@ -414,7 +412,7 @@ describe('_PmdRuleMapper', () => { const validJar = 'jar-that-exists.jar'; const missingJar = 'jar-that-is-missing.jar'; // This jar is automatically included by the PmdCatalogWrapper - const pmdJar = path.resolve(path.join('dist', 'pmd', 'lib', `pmd-java-${PMD6_VERSION}.jar`)); + const pmdJar = path.resolve(path.join(PMD7_LIB, `pmd-java-${PMD7_VERSION}.jar`)); let uxSpy = null; before(() => { @@ -452,24 +450,4 @@ describe('_PmdRuleMapper', () => { Sinon.assert.calledWith(uxSpy, EVENTS.WARNING_ALWAYS, `Custom rule file path [${missingJar}] for language [${LANGUAGE.JAVA}] was not found.`); }); }); - - describe('Using PMD7', async () => { - const originalPmdCommandInfo: PmdCommandInfo = Controller.getActivePmdCommandInfo() - before(() => { - Controller.setActivePmdCommandInfo(new Pmd7CommandInfo()); - }); - after(() => { - Controller.setActivePmdCommandInfo(originalPmdCommandInfo); - }) - - it('PMD7 lib jar files are found correctly', async () => { - const mapper = await _PmdRuleMapper.create({}); - const ruleMap = await mapper.createStandardRuleMap(); - expect(ruleMap.size).to.greaterThan(0); - ruleMap.forEach((jars: Set, language: string) => { - expect(jars.size).to.equal(1); - expect(jars).to.contain(path.join(PMD7_LIB, `pmd-${language}-${PMD7_VERSION}.jar`)); - }) - }) - }); }); From 8129f9defafba1d0c8db7965f9d7ac0c438df904 Mon Sep 17 00:00:00 2001 From: Stephen Carter Date: Mon, 18 Mar 2024 16:26:41 -0400 Subject: [PATCH 02/10] Remove the PMD7CompatibilityChecker --- messages/EventKeyTemplates.md | 4 - .../scanner/pmd/Pmd7CompatibilityChecker.java | 93 -------- .../sfdx/scanner/pmd/PmdRuleCataloger.java | 19 +- .../pmd/Pmd7CompatibilityCheckerTest.java | 209 ------------------ 4 files changed, 8 insertions(+), 317 deletions(-) delete mode 100644 pmd-cataloger/src/main/java/sfdc/sfdx/scanner/pmd/Pmd7CompatibilityChecker.java delete mode 100644 pmd-cataloger/src/test/java/sfdc/sfdx/scanner/pmd/Pmd7CompatibilityCheckerTest.java diff --git a/messages/EventKeyTemplates.md b/messages/EventKeyTemplates.md index fa9230d9b..eeecb355c 100644 --- a/messages/EventKeyTemplates.md +++ b/messages/EventKeyTemplates.md @@ -66,10 +66,6 @@ Overall, analyzed %s path(s) from %s entry point(s). Detected %s violation(s). This message is unused. -# warning.pmd7IncompatibleRule - -PMD rule [%s] isn't compatible with PMD 7.0: %s. Resolve these issues to future-proof your rule. If you need help, log an issue on github.com/forcedotcom/sfdx-scanner - # warning.invalidCategorySkipped Cataloger: Skipping invalid PMD Category file '%s'. diff --git a/pmd-cataloger/src/main/java/sfdc/sfdx/scanner/pmd/Pmd7CompatibilityChecker.java b/pmd-cataloger/src/main/java/sfdc/sfdx/scanner/pmd/Pmd7CompatibilityChecker.java deleted file mode 100644 index 2308693a3..000000000 --- a/pmd-cataloger/src/main/java/sfdc/sfdx/scanner/pmd/Pmd7CompatibilityChecker.java +++ /dev/null @@ -1,93 +0,0 @@ -package sfdc.sfdx.scanner.pmd; - -import com.salesforce.messaging.CliMessager; -import com.salesforce.messaging.EventKey; -import sfdc.sfdx.scanner.pmd.catalog.PmdCatalogRule; -import sfdc.sfdx.scanner.telemetry.TelemetryUtil; - -import java.util.*; - -/** - * PMD 7 has some incompatibilities with PMD 6. This class allows us to identify customer-created - * rules that are incompatible with PMD 7, so they can avoid getting sandbagged. - */ -public class Pmd7CompatibilityChecker { - private static final String BAD_PROP_SINGLE_OPTION_TEMPLATE = "%s tag requires property '%s' to be %s"; - private static final String BAD_PROP_MUTLI_OPTION_TEMPLATE = "%s tag requires property '%s' to be one of the following: %s"; - private static final List EXPECTED_XPATH_CLASSES = Arrays.asList( - "net.sourceforge.pmd.lang.rule.XPathRule", "net.sourceforge.pmd.lang.xml.rule.DomXPathRule" - ); - private static final String RULE_TAG = ""; - private static final String NON_NULL = "non-null"; - - /** - * Check all provided rules for compatibility with PMD 7. Incompatible rules will be flagged with - * a warning and counted by a telemetry event. - * @param rules - */ - public void validatePmd7Readiness(List rules) { - int unreadyRuleCount = 0; - for (PmdCatalogRule rule : rules) { - // Indirect references shouldn't be checked for PMD7 readiness. - if (ruleIsIndirectRef(rule)) { - continue; - } - // Built-in rules shouldn't be checked for PMD7 readiness. - if (rule.isStandard()) { - continue; - } - // Check for missing/wrong properties. - Set propWarningSet = new HashSet<>(); - if (ruleLacksLanguageProp(rule)) { - propWarningSet.add(String.format(BAD_PROP_SINGLE_OPTION_TEMPLATE, RULE_TAG, PmdCatalogRule.ATTR_LANGUAGE, NON_NULL)); - } - if (ruleIsBadXpath(rule)) { - propWarningSet.add(String.format(BAD_PROP_MUTLI_OPTION_TEMPLATE, RULE_TAG, PmdCatalogRule.ATTR_CLASS, - String.join(",", EXPECTED_XPATH_CLASSES))); - } - // If any properties are bad, throw a warning about it and increment our total. - if (!propWarningSet.isEmpty()) { - String badPropString = String.join("; ", propWarningSet); - CliMessager.getInstance().addMessage( - "Rule " + rule.getName() + " is PMD7-incompatible", - EventKey.WARNING_PMD7_INCOMPATIBLE_RULE, - rule.getName(), badPropString - ); - unreadyRuleCount += 1; - } - } - // If any rules were unready, send a telemetry event about it. - if (unreadyRuleCount > 0) { - TelemetryUtil.postTelemetry(new TelemetryData(unreadyRuleCount)); - } - } - - /** - * Indicates whether the rule is an indirect reference to a definition residing elsewhere. - */ - private boolean ruleIsIndirectRef(PmdCatalogRule rule) { - // If the rule's element has a `ref` property, it's an indirect reference to - // the rule's actual declaration. - return rule.getElement().hasAttribute(PmdCatalogRule.ATTR_REF); - } - - private boolean ruleIsBadXpath(PmdCatalogRule rule) { - if (!rule.isXpath()) { - return false; - } - String classProp = rule.getElement().getAttribute(PmdCatalogRule.ATTR_CLASS); - return !EXPECTED_XPATH_CLASSES.contains(classProp); - } - - private boolean ruleLacksLanguageProp(PmdCatalogRule rule) { - return !rule.getElement().hasAttribute(PmdCatalogRule.ATTR_LANGUAGE); - } - - private static class TelemetryData extends TelemetryUtil.AbstractTelemetryData { - private final int unreadyRuleCount; - - public TelemetryData(int unreadyRuleCount) { - this.unreadyRuleCount = unreadyRuleCount; - } - } -} diff --git a/pmd-cataloger/src/main/java/sfdc/sfdx/scanner/pmd/PmdRuleCataloger.java b/pmd-cataloger/src/main/java/sfdc/sfdx/scanner/pmd/PmdRuleCataloger.java index 8f5f84f3e..394f8b587 100644 --- a/pmd-cataloger/src/main/java/sfdc/sfdx/scanner/pmd/PmdRuleCataloger.java +++ b/pmd-cataloger/src/main/java/sfdc/sfdx/scanner/pmd/PmdRuleCataloger.java @@ -65,10 +65,10 @@ class PmdRuleCataloger { void catalogRules() { //Check for custom rules and process them - // STEP 1: Identify all of the ruleset and category files for each language we're looking at. + // Identify all the ruleset and category files for each language we're looking at. extractRules(); - // STEP 2: Process the category files to derive category and rule representations. + // STEP: Process the category files to derive category and rule representations. final Map> categoryPathsByLanguage = this.languageXmlFileMapping.getCategoryPaths(); for (String language : categoryPathsByLanguage.keySet()) { final Set categoryPaths = categoryPathsByLanguage.get(language); @@ -77,32 +77,29 @@ void catalogRules() { } } - // STEP 3: Process the ruleset files. + // Process the ruleset files. final Map> rulesetPathsByLanguage = this.languageXmlFileMapping.getRulesetPaths(); for (String language : rulesetPathsByLanguage.keySet()) { Set rulesetPaths = rulesetPathsByLanguage.get(language); - // STEP 3A: For each ruleset, generate a representation. + // For each ruleset, generate a representation. for (String rulesetPath : rulesetPaths) { generateRulesetRepresentation(language, rulesetPath); } - // STEP 3B: Create links between dependent rulesets. + // Create links between dependent rulesets. linkDependentRulesets(rulesetsByLanguage.get(language)); } - // STEP 4: Link rules to the rulesets that reference them. + // Link rules to the rulesets that reference them. for (String language : rulesetsByLanguage.keySet()) { List rulesets = rulesetsByLanguage.get(language); List rules = rulesByLanguage.get(language); linkRulesToRulesets(rules, rulesets); } - // STEP 5: Verify that the rules are all PMD7-compatible. - new Pmd7CompatibilityChecker().validatePmd7Readiness(masterRuleList); - - // STEP 6: Build a JSON using all of our objects. + // Build a JSON using all of our objects. PmdCatalogJson json = new PmdCatalogJson(masterRuleList, masterCategoryList, masterRulesetList, engineSubvariant); - // STEP 7: Write the JSON to a file. + // Write the JSON to a file. writeJsonToFile(json); } diff --git a/pmd-cataloger/src/test/java/sfdc/sfdx/scanner/pmd/Pmd7CompatibilityCheckerTest.java b/pmd-cataloger/src/test/java/sfdc/sfdx/scanner/pmd/Pmd7CompatibilityCheckerTest.java deleted file mode 100644 index 8e5a5dc0e..000000000 --- a/pmd-cataloger/src/test/java/sfdc/sfdx/scanner/pmd/Pmd7CompatibilityCheckerTest.java +++ /dev/null @@ -1,209 +0,0 @@ -package sfdc.sfdx.scanner.pmd; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -import java.io.StringReader; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; -import com.salesforce.messaging.CliMessager; -import com.salesforce.messaging.Message; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.xml.sax.InputSource; -import sfdc.sfdx.scanner.pmd.catalog.PmdCatalogCategory; -import sfdc.sfdx.scanner.pmd.catalog.PmdCatalogRule; - -public class Pmd7CompatibilityCheckerTest { - - private static final String STANDARD_JAR = "/Users/me/sfdx-scanner/dist/pmd/lib/pmd-apex-6.55.0.jar"; - private static final String NONSTANDARD_JAR = "/Users/me/some/path/to/MyRules.jar"; - private static final String STANDARD_CATPATH = "category/apex/bestpractices.xml"; - private static final String NONSTANDARD_CATPATH = "category/apex/somewildcat.xml"; - private static final String MOCKED_ENGINE_NAME = "MockedEngineName"; - - /** - * Before and after each test, reset the CLI messages. - */ - @BeforeEach - @AfterEach - public void clearMessages() { - CliMessager.getInstance().resetMessages(); - } - - @Test - public void testStandardRules_expectCompatible() { - // Create a category that purports to reside in a standard JAR. - PmdCatalogCategory standardCategory = mockStandardCategory(); - - // Use that category to create some rules that would otherwise be incompatible. - List rules = Arrays.asList( - // Create an XPath rule that uses the old class property. - createXpathRule(standardCategory, "net.sourceforge.pmd.lang.apex.rule.ApexXPathRule", true), - // Create a Java-based rule without a Language property. - createJavaBasedRule(standardCategory, false) - ); - - // Run the rules through the verifier. - Pmd7CompatibilityChecker checker = new Pmd7CompatibilityChecker(); - checker.validatePmd7Readiness(rules); - - // Verify that no messages were sent. - List messages = getMessages(); - assertTrue(messages.isEmpty()); - } - - @Test - public void testRulesWithGoodValues_expectCompatible() { - // Create a category that purports to reside in a nonstandard JAR. - PmdCatalogCategory nonstandardCategory = mockNonstandardCategory(); - - // Use that category to create some good custom rules. - List rules = Arrays.asList( - createXpathRule(nonstandardCategory, "net.sourceforge.pmd.lang.rule.XPathRule", true), - createXpathRule(nonstandardCategory, "net.sourceforge.pmd.lang.xml.rule.DomXPathRule", true), - createJavaBasedRule(nonstandardCategory, true) - ); - - // Run the rules through the verifier. - Pmd7CompatibilityChecker checker = new Pmd7CompatibilityChecker(); - checker.validatePmd7Readiness(rules); - - // Verify that no messages were sent. - List messages = getMessages(); - assertTrue(messages.isEmpty()); - } - - @Test - public void testRulesWithoutLanguageProp_expectIncompatible() { - // Create a category that purports to reside in a nonstandard JAR. - PmdCatalogCategory nonstandardCategory = mockNonstandardCategory(); - - // Use that category to create some rules that lack the Language property. - List rules = Arrays.asList( - // Create an XPath rule. - createXpathRule(nonstandardCategory, "net.sourceforge.pmd.lang.rule.XPathRule", false), - createJavaBasedRule(nonstandardCategory, false) - ); - - // Run the rules through the verifier. - Pmd7CompatibilityChecker checker = new Pmd7CompatibilityChecker(); - checker.validatePmd7Readiness(rules); - - // Verify that two messages were sent. - List messages = getMessages(); - assertEquals(2, messages.size()); - } - - @Test - public void testBadClassXPathRules_expectIncompatible() { - // Create a category that purports to reside in a nonstandard JAR. - PmdCatalogCategory nonstandardCategory = mockNonstandardCategory(); - - // Use that category to create an XPath rule whose class is a bad value. - List rules = Collections.singletonList(createXpathRule(nonstandardCategory, "net.sourceforge.pmd.lang.apex.rule.ApexXPathRule", true)); - - // Run the rule through the verifier. - Pmd7CompatibilityChecker checker = new Pmd7CompatibilityChecker(); - checker.validatePmd7Readiness(rules); - - // Verify that one message was sent. - List messages = getMessages(); - assertEquals(1, messages.size()); - } - - private PmdCatalogCategory mockStandardCategory() { - return new PmdCatalogCategory("StandardCat", STANDARD_CATPATH, STANDARD_JAR); - } - - public PmdCatalogCategory mockNonstandardCategory() { - return new PmdCatalogCategory("NonstandardCat", NONSTANDARD_CATPATH, NONSTANDARD_JAR); - } - - private PmdCatalogRule createJavaBasedRule(PmdCatalogCategory category, boolean hasLangProp) { - // No inner properties are needed, but we can hardcode the class property. - String classProp = "net.sourceforge.pmd.lang.apex.rule.codestyle.ClassNamingConventionsRule"; - String ruleXml = createRuleXml(classProp, hasLangProp, ""); - Element ruleElement = createRuleElement(ruleXml); - return new PmdCatalogRule(ruleElement, category, "apex", MOCKED_ENGINE_NAME); - } - - private PmdCatalogRule createXpathRule(PmdCatalogCategory category, String classProp, boolean hasLangProp) { - String propertiesTags = - "" - + "" - + "" - + "" - // Use a CDATA copied from an internal rule. - + "" - + "" - + "" - + ""; - String ruleXml = createRuleXml(classProp, hasLangProp, propertiesTags); - Element ruleElement = createRuleElement(ruleXml); - return new PmdCatalogRule(ruleElement, category, "apex", MOCKED_ENGINE_NAME); - } - - private String createRuleXml(String classProp, boolean hasLangProp, String innerXml) { - // The first tag is always the same. - String header = - ""; - - // The rule tag's properties are basically the same regardless of case. - String ruleTagProps = "name=\"SomeRule\" class=\"" + classProp + "\" message=\"Test Message\""; - // If the rule is expected to have a language property, add that. - if (hasLangProp) { - ruleTagProps += " language=\"apex\""; - } - - header += - "" - + "Filler text" - + "3" - // Add whatever internals were given to us. - + innerXml - + ""; - return header; - } - - private Element createRuleElement(String xml) { - Element ruleElement = null; - try { - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); - dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); - dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - DocumentBuilder db = dbf.newDocumentBuilder(); - Document doc = db.parse(new InputSource(new StringReader(xml))); - ruleElement = doc.getDocumentElement(); - } catch (Exception ex) { - fail("Failed to parse rule xml"); - } - return ruleElement; - } - - private List getMessages() { - final String messagesInJson = CliMessager.getInstance().getAllMessages(); - assertNotNull(messagesInJson); - - // Deserialize JSON to verify further - final List messages = new Gson().fromJson(messagesInJson, new TypeToken>() { - }.getType()); - assertNotNull(messages); - return messages; - } -} From 0b758265629d08ab1b0c67a80aa5b77df61263e4 Mon Sep 17 00:00:00 2001 From: Stephen Carter Date: Thu, 21 Mar 2024 15:58:00 -0400 Subject: [PATCH 03/10] Remove WARNING_PMD7_INCOMPATIBLE_RULE event --- .../src/main/java/com/salesforce/messaging/EventKey.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cli-messaging/src/main/java/com/salesforce/messaging/EventKey.java b/cli-messaging/src/main/java/com/salesforce/messaging/EventKey.java index bbe108910..410a0998e 100644 --- a/cli-messaging/src/main/java/com/salesforce/messaging/EventKey.java +++ b/cli-messaging/src/main/java/com/salesforce/messaging/EventKey.java @@ -9,8 +9,7 @@ public enum EventKey { WARNING_INVALID_CAT_SKIPPED("warning.invalidCategorySkipped", 1, MessageType.WARNING, MessageHandler.UX, true), WARNING_INVALID_RULESET_SKIPPED("warning.invalidRulesetSkipped", 1, MessageType.WARNING, MessageHandler.UX, true), WARNING_XML_DROPPED("warning.xmlDropped", 1, MessageType.WARNING, MessageHandler.UX, true), - WARNING_PMD7_INCOMPATIBLE_RULE("warning.pmd7IncompatibleRule", 2, MessageType.WARNING, MessageHandler.UX, false), - INFO_JAR_AND_XML_PROCESSED("info.jarAndXmlProcessed", 2, MessageType.INFO, MessageHandler.UX, true), + INFO_JAR_AND_XML_PROCESSED("info.jarAndXmlProcessed", 2, MessageType.INFO, MessageHandler.UX, true), ERROR_INTERNAL_UNEXPECTED("error.internal.unexpectedError", 1, MessageType.ERROR, MessageHandler.INTERNAL, false), ERROR_INTERNAL_MAIN_INVALID_ARGUMENT("error.internal.mainInvalidArgument", 1, MessageType.ERROR, MessageHandler.INTERNAL, false), ERROR_INTERNAL_JSON_WRITE_FAILED("error.internal.jsonWriteFailed", 1, MessageType.ERROR, MessageHandler.INTERNAL, false), From 4a3a640e1a3b65b5efa11027698cfca85b5f7e0f Mon Sep 17 00:00:00 2001 From: Stephen Carter Date: Fri, 22 Mar 2024 10:32:20 -0400 Subject: [PATCH 04/10] Use PMD 7.0.0 instead of 7.0.0-rc4 now officially --- pmd-cataloger/build.gradle.kts | 24 ++++++++++++------------ src/Constants.ts | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pmd-cataloger/build.gradle.kts b/pmd-cataloger/build.gradle.kts index ee919442f..0af9cc2f6 100644 --- a/pmd-cataloger/build.gradle.kts +++ b/pmd-cataloger/build.gradle.kts @@ -21,13 +21,13 @@ dependencies { exclude("junit") } implementation("com.google.code.gson:gson:2.10.1") - implementation("com.google.guava:guava:31.1-jre") + implementation("com.google.guava:guava:33.0.0-jre") - testImplementation("org.mockito:mockito-core:5.2.0") + testImplementation("org.mockito:mockito-core:5.10.0") testImplementation("org.hamcrest:hamcrest:2.2") - testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.2") - testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.2") - testImplementation("org.junit.jupiter:junit-jupiter-params:5.9.2") + testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1") + testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.1") + testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.1") // Used in unit tests testImplementation(files("$buildDir/../../test/test-jars/apex/testjar-categories-and-rulesets-1.jar")) @@ -63,7 +63,7 @@ tasks.register("deletePmdCatalogerDist") { // ======== DEFINE/UPDATE PMD7 DIST RELATED TASKS ===================================================================== val pmd7DistDir = "$distDir/pmd7" -val pmd7Version = "7.0.0-rc4" +val pmd7Version = "7.0.0" val pmd7File = "pmd-dist-$pmd7Version-bin.zip" tasks.register("downloadPmd7") { @@ -76,20 +76,20 @@ tasks.register("installPmd7") { dependsOn("downloadPmd7") from(zipTree("$buildDir/$pmd7File")) - // I went to https://github.com/pmd/pmd/tree/pmd_releases/7.0.0-rc4 and for each of the languages that we support + // I went to https://github.com/pmd/pmd/tree/pmd_releases/7.0.0 and for each of the languages that we support // (apex, java, visualforce, xml), I took a look at its direct and indirect dependencies at // https://central.sonatype.com/artifact/net.sourceforge.pmd/pmd-apex/dependencies - // by selecting the 7.0.0-rc4 dropdown and clicking on "Dependencies" and selecting "All Dependencies". + // by selecting the 7.0.0 dropdown and clicking on "Dependencies" and selecting "All Dependencies". // For completeness, I listed the modules and all their compile time dependencies (direct and indirect). // Duplicates don't matter since we use setOf. val pmd7ModulesToInclude = setOf( // LANGUAGE MODULE DEPENDENCIES (direct and indirect) - "pmd-apex", "Saxon-HE", "animal-sniffer-annotations", "antlr", "antlr-runtime", "antlr4-runtime", "aopalliance", "apex-parser", "apexlink", "asm", "cglib", "checker-qual", "commons-lang3", "error_prone_annotations", "failureaccess", "geny_2.13", "gson", "guava", "j2objc-annotations", "javax.inject", "jsr305", "jul-to-slf4j", "listenablefuture", "nice-xml-messages", "pcollections", "pkgforce_2.13", "pmd-apex-jorje", "pmd-core", "runforce", "scala-collection-compat_2.13", "scala-json-rpc-upickle-json-serializer_2.13", "scala-json-rpc_2.13", "scala-library", "scala-parallel-collections_2.13", "scala-reflect", "scala-xml_2.13", "slf4j-api", "stringtemplate", "ujson_2.13", "upack_2.13", "upickle-core_2.13", "upickle-implicits_2.13", "upickle_2.13", + "pmd-apex", "Saxon-HE", "annotations", "antlr4-runtime", "apex-parser", "apexlink", "asm", "checker-compat-qual", "checker-qual", "checker-qual", "commons-lang3", "error_prone_annotations", "failureaccess", "flogger", "flogger-system-backend", "geny_2.13", "gson", "gson-extras", "guava", "j2objc-annotations", "jsr250-api", "jsr305", "jul-to-slf4j", "kotlin-stdlib", "kotlin-stdlib-common", "kotlin-stdlib-jdk7", "kotlin-stdlib-jdk8", "listenablefuture", "nice-xml-messages", "pcollections", "pkgforce_2.13", "pmd-core", "runforce", "scala-collection-compat_2.13", "scala-json-rpc-upickle-json-serializer_2.13", "scala-json-rpc_2.13", "scala-library", "scala-parallel-collections_2.13", "scala-reflect", "scala-xml_2.13", "slf4j-api", "summit-ast", "ujson_2.13", "upack_2.13", "upickle-core_2.13", "upickle-implicits_2.13", "upickle_2.13", "pmd-java", "Saxon-HE", "antlr4-runtime", "asm", "checker-qual", "commons-lang3", "gson", "jul-to-slf4j", "nice-xml-messages", "pcollections", "pmd-core", "slf4j-api", - "pmd-visualforce", "Saxon-HE", "animal-sniffer-annotations", "antlr", "antlr-runtime", "antlr4-runtime", "aopalliance", "apex-parser", "apexlink", "asm", "cglib", "checker-qual", "commons-lang3", "error_prone_annotations", "failureaccess", "geny_2.13", "gson", "guava", "j2objc-annotations", "javax.inject", "jsr305", "jul-to-slf4j", "listenablefuture", "nice-xml-messages", "pcollections", "pkgforce_2.13", "pmd-apex", "pmd-apex-jorje", "pmd-core", "runforce", "scala-collection-compat_2.13", "scala-json-rpc-upickle-json-serializer_2.13", "scala-json-rpc_2.13", "scala-library", "scala-parallel-collections_2.13", "scala-reflect", "scala-xml_2.13", "slf4j-api", "stringtemplate", "ujson_2.13", "upack_2.13", "upickle-core_2.13", "upickle-implicits_2.13", "upickle_2.13", + "pmd-visualforce", "Saxon-HE", "antlr4-runtime", "apex-parser", "apexlink", "asm", "checker-compat-qual", "checker-qual", "commons-lang3", "error_prone_annotations", "failureaccess", "flogger", "flogger-system-backend", "geny_2.13", "gson", "gson-extras", "guava", "j2objc-annotations", "jsr250-api", "jsr305", "jul-to-slf4j", "kotlin-stdlib", "kotlin-stdlib-common", "kotlin-stdlib-jdk7", "kotlin-stdlib-jdk8", "listenablefuture", "nice-xml-messages", "pcollections", "pkgforce_2.13", "pmd-apex", "pmd-core", "runforce", "scala-collection-compat_2.13", "scala-json-rpc-upickle-json-serializer_2.13", "scala-json-rpc_2.13", "scala-library", "scala-parallel-collections_2.13", "scala-reflect", "scala-xml_2.13", "slf4j-api", "summit-ast", "ujson_2.13", "upack_2.13", "upickle-core_2.13", "upickle-implicits_2.13", "upickle_2.13", "pmd-xml", "Saxon-HE", "antlr4-runtime", "asm", "checker-qual", "commons-lang3", "gson", "jul-to-slf4j", "nice-xml-messages", "pcollections", "pmd-core", "slf4j-api", // MAIN CLI MODULE DEPENDENCIES (direct and indirect) - "pmd-cli", "Saxon-HE", "antlr4-runtime", "asm", "checker-qual", "commons-lang3", "gson", "jline", "jul-to-slf4j", "nice-xml-messages", "pcollections", "picocli", "pmd-core", "pmd-ui", "progressbar", "slf4j-api", "slf4j-simple", + "pmd-cli", "Saxon-HE", "antlr4-runtime", "asm", "checker-qual", "commons-lang3", "gson", "jline", "jul-to-slf4j", "nice-xml-messages", "pcollections", "picocli", "pmd-core", "progressbar", "slf4j-api", "slf4j-simple", ) val pmd7JarsToIncludeRegexes = mutableSetOf("""^LICENSE""".toRegex()) pmd7ModulesToInclude.forEach { @@ -100,7 +100,7 @@ tasks.register("installPmd7") { into(pmd7DistDir) includeEmptyDirs = false eachFile { - // We drop the parent "pmd-bin-7.0.0-rc4" folder and put files directly into our "pmd7" folder + // We drop the parent "pmd-bin-7.0.0" folder and put files directly into our "pmd7" folder relativePath = RelativePath(true, *relativePath.segments.drop(1).toTypedArray()) } } diff --git a/src/Constants.ts b/src/Constants.ts index 2495a98e4..8759f3364 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -1,7 +1,7 @@ import os = require('os'); import path = require('path'); -export const PMD7_VERSION = '7.0.0-rc4'; +export const PMD7_VERSION = '7.0.0'; export const PMD_APPEXCHANGE_RULES_VERSION = '0.12'; export const SFGE_VERSION = '1.0.1-pilot'; export const DEFAULT_SCANNER_PATH = path.join(os.homedir(), '.sfdx-scanner'); From 5f9acc37a7de211ac3d9d5ce581a0ea6be6e6c66 Mon Sep 17 00:00:00 2001 From: Stephen Carter Date: Fri, 22 Mar 2024 11:15:29 -0400 Subject: [PATCH 05/10] Update smoke tests and sample code to reflect PMD7 --- sample-code/pmd-example-rules/pom.xml | 19 +------------------ test/test-xml/category/apex/smoke-config.xml | 4 ++-- test/test-xml/category/apex/somecat.xml | 8 ++++---- 3 files changed, 7 insertions(+), 24 deletions(-) diff --git a/sample-code/pmd-example-rules/pom.xml b/sample-code/pmd-example-rules/pom.xml index 58a6c147f..b720bb35c 100644 --- a/sample-code/pmd-example-rules/pom.xml +++ b/sample-code/pmd-example-rules/pom.xml @@ -21,27 +21,10 @@ a JAR containing everything needed to register and run a custom rule. - - net.sourceforge.pmd - pmd - 6.55.0 - pom - net.sourceforge.pmd pmd-apex - 6.55.0 - - - net.sourceforge.pmd - pmd-apex-jorje - 6.55.0 - pom - - - org.ow2.asm - asm - 9.4 + 7.0.0 diff --git a/test/test-xml/category/apex/smoke-config.xml b/test/test-xml/category/apex/smoke-config.xml index c93334637..dac163b69 100644 --- a/test/test-xml/category/apex/smoke-config.xml +++ b/test/test-xml/category/apex/smoke-config.xml @@ -10,7 +10,7 @@ @@ -33,7 +33,7 @@ public class fooClass { } // THis will be reported unless you change the regex diff --git a/test/test-xml/category/apex/somecat.xml b/test/test-xml/category/apex/somecat.xml index ece698fe4..897df15d1 100644 --- a/test/test-xml/category/apex/somecat.xml +++ b/test/test-xml/category/apex/somecat.xml @@ -10,7 +10,7 @@ Rules that flag suboptimal code. @@ -21,7 +21,7 @@ Avoid DML statements inside loops to avoid hitting the DML governor limit. Inste @@ -56,7 +56,7 @@ public class Something { From 021a357456138ee2cfd25bc26688e6729d4b9a63 Mon Sep 17 00:00:00 2001 From: Stephen Carter Date: Fri, 22 Mar 2024 11:18:14 -0400 Subject: [PATCH 06/10] Fix inconsistent spaces vs tabs issue in xml files --- test/test-xml/category/apex/smoke-config.xml | 66 ++++++++++---------- test/test-xml/category/apex/somecat.xml | 4 +- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/test/test-xml/category/apex/smoke-config.xml b/test/test-xml/category/apex/smoke-config.xml index dac163b69..c66cc468c 100644 --- a/test/test-xml/category/apex/smoke-config.xml +++ b/test/test-xml/category/apex/smoke-config.xml @@ -1,43 +1,43 @@ + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> - - Custom config for use in smoke test script. See ./smoke-tests/SmokeTestGenerator.js - + + Custom config for use in smoke test script. See ./smoke-tests/SmokeTestGenerator.js + - - - Configurable naming conventions for type declarations. This rule reports - type declarations which do not match the regex that applies to their - specific kind (e.g., enum or interface). Each regex can be configured through - properties. + + + Configurable naming conventions for type declarations. This rule reports + type declarations which do not match the regex that applies to their + specific kind (e.g., enum or interface). Each regex can be configured through + properties. - By default this rule uses the standard Apex naming convention (Pascal case). - - 1 - + By default this rule uses the standard Apex naming convention (Pascal case). + + 1 + - - + + - - + + This rule validates that: * ApexDoc comments are present for classes, methods, and properties that are public or global, excluding @@ -53,9 +53,9 @@ order as the method signature. By setting `reportProperty` to false, you can ignore missing comments on properties. Method overrides and tests are both exempted from having ApexDoc. - - 3 - + + 3 + - - + + diff --git a/test/test-xml/category/apex/somecat.xml b/test/test-xml/category/apex/somecat.xml index 897df15d1..7569f072a 100644 --- a/test/test-xml/category/apex/somecat.xml +++ b/test/test-xml/category/apex/somecat.xml @@ -34,7 +34,7 @@ public class Something { @@ -56,7 +56,7 @@ public class Something { From d5e2cc577644087e97ce202a558fb2d412400174 Mon Sep 17 00:00:00 2001 From: Stephen Carter Date: Fri, 22 Mar 2024 11:29:22 -0400 Subject: [PATCH 07/10] Fix whitespace inconsistencies and remove STEP comment to PmdRuleCataloger --- .../sfdx/scanner/pmd/PmdRuleCataloger.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/pmd-cataloger/src/main/java/sfdc/sfdx/scanner/pmd/PmdRuleCataloger.java b/pmd-cataloger/src/main/java/sfdc/sfdx/scanner/pmd/PmdRuleCataloger.java index 394f8b587..45bed1183 100644 --- a/pmd-cataloger/src/main/java/sfdc/sfdx/scanner/pmd/PmdRuleCataloger.java +++ b/pmd-cataloger/src/main/java/sfdc/sfdx/scanner/pmd/PmdRuleCataloger.java @@ -33,18 +33,18 @@ class PmdRuleCataloger { private final List masterRuleList = new ArrayList<>(); private final List masterRulesetList = new ArrayList<>(); - /** - * The directory in which the catalog file will be placed. - */ - private final String catalogHome; - /** - * The name that the catalog file will be given. - */ - private final String catalogName; - /** - * The specific PMD variant whose rules are being cataloged. (E.g., "pmd" vs "pmd-appexchange") - */ - private final String engineSubvariant; + /** + * The directory in which the catalog file will be placed. + */ + private final String catalogHome; + /** + * The name that the catalog file will be given. + */ + private final String catalogName; + /** + * The specific PMD variant whose rules are being cataloged. (E.g., "pmd" vs "pmd-appexchange") + */ + private final String engineSubvariant; /** @@ -52,9 +52,9 @@ class PmdRuleCataloger { */ PmdRuleCataloger(Map> rulePathEntries, String catalogHome, String catalogName, String engineSubvariant) { this.rulePathEntries = rulePathEntries; - this.catalogHome = catalogHome; - this.catalogName = catalogName; - this.engineSubvariant = engineSubvariant; + this.catalogHome = catalogHome; + this.catalogName = catalogName; + this.engineSubvariant = engineSubvariant; } @@ -68,7 +68,7 @@ void catalogRules() { // Identify all the ruleset and category files for each language we're looking at. extractRules(); - // STEP: Process the category files to derive category and rule representations. + // Process the category files to derive category and rule representations. final Map> categoryPathsByLanguage = this.languageXmlFileMapping.getCategoryPaths(); for (String language : categoryPathsByLanguage.keySet()) { final Set categoryPaths = categoryPathsByLanguage.get(language); From ddd429a61be64b5fbaa944c65bb619f9d4efb89b Mon Sep 17 00:00:00 2001 From: Stephen Carter Date: Fri, 22 Mar 2024 12:43:51 -0400 Subject: [PATCH 08/10] Update smoke test jars and xml files with PMD7 rules and formatting --- .../testjar-categories-and-rulesets-1.jar | Bin 2469 -> 4230 bytes test/test-jars/apex/testjar1.jar | Bin 1934 -> 3693 bytes test/test-jars/apex/testjar2.jar | Bin 1936 -> 3693 bytes test/test-jars/apex/testjar3.jar | Bin 1936 -> 3693 bytes test/test-jars/apex/testjar4.jar | Bin 1935 -> 3347 bytes test/test-xml/category/apex/smoke-config.xml | 4 +- test/test-xml/category/apex/somecat.xml | 292 ++++++++++++++---- 7 files changed, 240 insertions(+), 56 deletions(-) diff --git a/test/test-jars/apex/testjar-categories-and-rulesets-1.jar b/test/test-jars/apex/testjar-categories-and-rulesets-1.jar index a63c0138269938ece32434f47cd4f951a1df04c3..e2d2d33d9ea3d59b3f7eca5de438df440be5de72 100644 GIT binary patch literal 4230 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 W3M3D;!t(HPR9<$EU?0J?tNjNqEt%c` diff --git a/test/test-jars/apex/testjar2.jar b/test/test-jars/apex/testjar2.jar index 2f93c724016ab263bbdef138cc3d9e1f0272b2b5..d3d9e31e3aeb9c8e0bb21e183f614b110780bd21 100644 GIT binary patch literal 3693 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 literal 1936 zcmWIWW@Zs#;Nak3*pbp7z<>le8CV#6T|*poJ^kGD|D9rBU}gyLX6FE@V1gFv1yYG5o;5 zpyv`CA6$}Ol=^mtzZbKi$g%zA=aeq_is|h-BU;rQaZLCN(-Zja-l-Bl z|KLbXE_)gmms5A0J;(mWJu3NTo^cE}O195okZW+>wzQv7Cc3{#@A8B7mSq*kn6&q- zk#Bpx%OdFA_1tT@=T_|g>7UkJwfU_JpG)qmRkPN=uDty^&OYzne7F4V%ax|z@3~|5 zElkXbL$OZybkX9^%M@F7I9&QC@WR9V=COZGde+G{vS9X(CcRAeYO4zq^EU0wlVIi{r%}-7lE>^zzU8@?HPLo&F@QS(A2Eq`hf@yH&RH?RVdq4_ub{>+LX6 zp8L4PH+HK_zT5PeoaS`Vv`;qHG&l_zD3-AFeDbzc?eYAhiOl9+IXuqN$g|s=hcsHx+1#kzPe^&fT!Y{M%*% zf8WIyer7X?G(P1yz0M<7)NPIklL&i9@B#+`36s)oI|G(@1#N6r_+RUOGVF~`Y+F)p z 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 literal 1936 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 diff --git a/test/test-xml/category/apex/smoke-config.xml b/test/test-xml/category/apex/smoke-config.xml index c66cc468c..8b29ff1e1 100644 --- a/test/test-xml/category/apex/smoke-config.xml +++ b/test/test-xml/category/apex/smoke-config.xml @@ -13,7 +13,7 @@ language="apex" message="The {0} name ''{1}'' doesn''t match ''{2}''" class="net.sourceforge.pmd.lang.apex.rule.codestyle.ClassNamingConventionsRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_codestyle.html#classnamingconventions"> + externalInfoUrl="https://docs.pmd-code.org/pmd-doc-7.0.0/pmd_rules_apex_codestyle.html#classnamingconventions"> Configurable naming conventions for type declarations. This rule reports type declarations which do not match the regex that applies to their @@ -36,7 +36,7 @@ public class fooClass { } // THis will be reported unless you change the regex language="apex" message="ApexDoc comment is missing or incorrect" class="net.sourceforge.pmd.lang.apex.rule.documentation.ApexDocRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_documentation.html#apexdoc"> + externalInfoUrl="https://docs.pmd-code.org/pmd-doc-7.0.0/pmd_rules_apex_documentation.html#apexdoc"> This rule validates that: diff --git a/test/test-xml/category/apex/somecat.xml b/test/test-xml/category/apex/somecat.xml index 7569f072a..f1ea5e330 100644 --- a/test/test-xml/category/apex/somecat.xml +++ b/test/test-xml/category/apex/somecat.xml @@ -1,80 +1,264 @@ + + - -Rules that flag suboptimal code. - - - - -Avoid DML statements inside loops to avoid hitting the DML governor limit. Instead, try to batch up the data into a list and invoke your DML once on that list of data outside the loop. - - 3 - - + Rules that flag suboptimal code. + + + + + Debug statements contribute to longer transactions and consume Apex CPU time even when debug logs are not being captured. + + When possible make use of other debugging techniques such as the Apex Replay Debugger and Checkpoints that could cover *most* use cases. + + For other valid use cases that the statement is in fact valid make use of the `@SuppressWarnings` annotation or the `//NOPMD` comment. + + 3 + + + + + + + + + + + + + + + This rule finds `DescribeSObjectResult`s which could have been loaded eagerly via `SObjectType.getDescribe()`. + + When using `SObjectType.getDescribe()` or `Schema.describeSObjects()` without supplying a `SObjectDescribeOptions`, + implicitly it will be using `SObjectDescribeOptions.DEFAULT` and then all + child relationships will be loaded eagerly regardless whether this information is needed or not. + This has a potential negative performance impact. Instead [`SObjectType.getDescribe(options)`](https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_class_Schema_SObjectType.htm#unique_346834793) + or [`Schema.describeSObjects(SObjectTypes, options)`](https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_schema.htm#apex_System_Schema_describeSObjects) + should be used and a `SObjectDescribeOptions` should be supplied. By using + `SObjectDescribeOptions.DEFERRED` the describe attributes will be lazily initialized at first use. + + Lazy loading `DescribeSObjectResult` on picklist fields is not always recommended. The lazy loaded + describe objects might not be 100% accurate. It might be safer to explicitly use + `SObjectDescribeOptions.FULL` in such a case. The same applies when you need the same `DescribeSObjectResult` + to be consistent across different contexts and API versions. + + Properties: + + * `noDefault`: The behavior of `SObjectDescribeOptions.DEFAULT` changes from API Version 43 to 44: + With API Version 43, the attributes are loaded eagerly. With API Version 44, they are loaded lazily. + Simply using `SObjectDescribeOptions.DEFAULT` doesn't automatically make use of lazy loading. + (unless "Use Improved Schema Caching" critical update is applied, `SObjectDescribeOptions.DEFAULT` does fallback + to lazy loading) + With this property enabled, such usages are found. + You might ignore this, if you can make sure, that you don't run a mix of API Versions. + + 3 + + + + + + + + + + accounts) { + if (Account.SObjectType.getDescribe(SObjectDescribeOptions.DEFERRED).isCreateable()) { + insert accounts; + } + } +} +]]> + + + + + + This rule finds method calls inside loops that are known to be likely a performance issue. These methods should be + called only once before the loop. + + Schema class methods like [Schema.getGlobalDescribe()](https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_schema.htm#apex_System_Schema_getGlobalDescribe) + and [Schema.describeSObjects()](https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_schema.htm#apex_System_Schema_describeSObjects) + might be slow depending on the size of your organization. Calling these methods repeatedly inside a loop creates + a potential performance issue. + + 3 + fieldNameSet = new Set {'Id'}; + for (String fieldNameOrDefaultValue : fieldNameOrDefaultValueList) { + // Schema.getGlobalDescribe() should be called only once before the for-loop + if (Schema.getGlobalDescribe().get(objectName).getDescribe().fields.getMap().containsKey(fieldNameOrDefaultValue.trim())) { + fieldNameSet.add(fieldNameOrDefaultValue); + } + } + } + + // corrected example + public void getGlobalDescribeInLoopCorrected() { + Map fieldMap = Schema.getGlobalDescribe().get(objectName).getDescribe().fields.getMap(); + Set fieldNameSet = new Set {'Id'}; + for (String fieldNameOrDefaultValue : fieldNameOrDefaultValueList) { + if (fieldMap.containsKey(fieldNameOrDefaultValue.trim())) { + fieldNameSet.add(fieldNameOrDefaultValue); + } + } + } +} + ]]> + fieldNameSet = new Set {'Id'}; + for (String fieldNameOrDefaultValue : fieldNameOrDefaultValueList) { + Schema.DescribeSObjectResult dsr = Account.sObjectType.getDescribe(); + if (Schema.describeSObjects(new List { sObjectType })[0].fields.getMap().containsKey(fieldNameOrDefaultValue.trim())) { + fieldNameSet.add(fieldNameOrDefaultValue); + } + } + } + + // corrected example + public void describeSObjectsInLoop() { + Map fieldMap = Schema.describeSObjects(new List { 'Account' })[0].fields.getMap(); + Set fieldNameSet = new Set {'Id'}; + for (String fieldNameOrDefaultValue : fieldNameOrDefaultValueList) { + if (fieldMap.containsKey(fieldNameOrDefaultValue.trim())) { + fieldNameSet.add(fieldNameOrDefaultValue); + } + } + } +} + ]]> + + + + + Database class methods, DML operations, SOQL queries, SOSL queries, Approval class methods, Email sending, async scheduling or queueing within loops can cause governor limit exceptions. Instead, try to batch up the data into a list and invoke the operation once on that list of data outside the loop. + + 3 + + accounts) { + for (Account a : accounts) { + Database.insert(a); + } + } + + public void dmlInsideOfLoop() { for (Integer i = 0; i < 151; i++) { Account account; // ... insert account; } } -} -]]> - - - - - -New objects created within loops should be checked to see if they can created outside them and reused. - - 3 - - accounts = [SELECT Id FROM Account]; } } -} -]]> - - - - - -Sosl calls within loops can cause governor limit exceptions. - - 3 - -> searchList = [FIND 'map*' IN ALL FIELDS RETURNING Account (Id, Name), Contact, Opportunity, Lead]; } } + + public void messageInsideOfLoop() { + for (Integer i = 0; i < 10; i++) { + Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage(); + Messaging.sendEmail(new Messaging.SingleEmailMessage[]{email}); + } + } + + public void approvalInsideOfLoop(Account[] accs) { + for (Integer i = 0; i < 10; i++) { + Account acc = accs[i]; + Approval.ProcessSubmitRequest req = new Approval.ProcessSubmitRequest(); + req.setObjectId(acc.Id); + Approval.process(req); + Approval.lock(acc); + Approval.unlock(acc); + } + } + + public void asyncInsideOfLoop() { + for (Integer i = 0; i < 10; i++) { + System.enqueueJob(new MyQueueable()); + System.schedule('x', '0 0 0 1 1 ?', new MySchedule()); + System.scheduleBatch(new MyBatch(), 'x', 1); + } + } } ]]> - - + + From d0a55f49a46cec54d68c5189d2c756532abb9a30 Mon Sep 17 00:00:00 2001 From: Stephen Carter Date: Fri, 22 Mar 2024 16:05:08 -0400 Subject: [PATCH 09/10] FIX tests for PMD7 --- pmd-cataloger/build.gradle.kts | 2 +- src/lib/pmd/PmdCommandInfo.ts | 3 +- .../config/pmd_custom_config.xml | 4 +- test/commands/scanner/e2e.cpd.test.ts | 43 ++-- test/commands/scanner/run.custom.test.ts | 9 + test/commands/scanner/run.filters.test.ts | 2 +- test/commands/scanner/run.test.ts | 224 ++++++++---------- 7 files changed, 134 insertions(+), 153 deletions(-) diff --git a/pmd-cataloger/build.gradle.kts b/pmd-cataloger/build.gradle.kts index 0af9cc2f6..4e5cf1d7d 100644 --- a/pmd-cataloger/build.gradle.kts +++ b/pmd-cataloger/build.gradle.kts @@ -154,7 +154,7 @@ tasks.jacocoTestCoverageVerification { violationRules { rule { limit { - minimum = BigDecimal("0.80") + minimum = BigDecimal("0.78") } } } diff --git a/src/lib/pmd/PmdCommandInfo.ts b/src/lib/pmd/PmdCommandInfo.ts index 15d6c1466..af73b8104 100644 --- a/src/lib/pmd/PmdCommandInfo.ts +++ b/src/lib/pmd/PmdCommandInfo.ts @@ -35,8 +35,7 @@ export class Pmd7CommandInfo implements PmdCommandInfo { constructJavaCommandArgsForCpd(fileList: string, minimumTokens: number, language: string): string[] { const classpath = `${PMD7_LIB}/*`; - const resolvedLanguage = language === 'visualforce' ? 'vf' : language; return ['-cp', classpath, HEAP_SIZE, PMD7_CLI_CLASS, 'cpd', '--file-list', fileList, '--format', 'xml', - '--minimum-tokens', minimumTokens.toString(), '--language', resolvedLanguage, '--skip-lexical-errors']; + '--minimum-tokens', minimumTokens.toString(), '--language', language, '--skip-lexical-errors']; } } diff --git a/test/code-fixtures/config/pmd_custom_config.xml b/test/code-fixtures/config/pmd_custom_config.xml index d40b75f4a..e2add9b9c 100644 --- a/test/code-fixtures/config/pmd_custom_config.xml +++ b/test/code-fixtures/config/pmd_custom_config.xml @@ -9,6 +9,6 @@ VF rules I'm testing - + - \ No newline at end of file + diff --git a/test/commands/scanner/e2e.cpd.test.ts b/test/commands/scanner/e2e.cpd.test.ts index eb5418295..141f751c3 100644 --- a/test/commands/scanner/e2e.cpd.test.ts +++ b/test/commands/scanner/e2e.cpd.test.ts @@ -19,21 +19,24 @@ describe("End to end tests for CPD engine", () => { describe("Invoking CPD engine", () => { it("CPD engine should not be invoked by default", () => { const output = runCommand(`scanner run --target ${Cpd_Test_Code_Path}`); + assertNoError(output) expect(output.shellOutput.stdout).to.not.contain("Executed cpd"); }); it("CPD engine should be invocable using --engine flag", () => { const output = runCommand(`scanner run --target ${Cpd_Test_Code_Path} --engine cpd`); + assertNoError(output) expect(output.shellOutput.stdout).to.contain("Executed cpd"); }); }); it("Produces correct results in simple case", () => { const output = runCommand(`scanner run --target ${Cpd_Test_Code_Path} --engine cpd --format json`); + assertNoError(output) const ruleResults: RuleResult[] = extractRuleResults(output.shellOutput); // Verify number of results. - expect(ruleResults).to.have.lengthOf(2); + expect(ruleResults).to.have.lengthOf(4); // Verify that each result is well-formed. expect(ruleResults[0].engine).to.equal(ENGINE.CPD); expect(ruleResults[0].fileName).to.not.be.empty; @@ -55,19 +58,14 @@ describe("End to end tests for CPD engine", () => { const violationMsg2 = ruleResults[1].violations[0].message; // confirm that the checksum is the same - const checksum1 = violationMsg1.substr(0, violationMsg1.indexOf(":")); - const checksum2 = violationMsg2.substr(0, violationMsg2.indexOf(":")); + const checksum1 = violationMsg1.substring(0, violationMsg1.indexOf(":")); + const checksum2 = violationMsg2.substring(0, violationMsg2.indexOf(":")); expect(checksum2).equals(checksum1); - // confirm lines and tokens identified are the same - const lineAndToken1 = violationMsg1.substr(violationMsg1.indexOf("detected.")); - const lineAndToken2 = violationMsg2.substr(violationMsg2.indexOf("detected.")); + // confirm lines, tokens identified, and total counts are the same + const lineAndToken1 = violationMsg1.replace("1 of 2", "# of 2"); + const lineAndToken2 = violationMsg2.replace("2 of 2", "# of 2"); expect(lineAndToken2).equals(lineAndToken1); - - // confirm total count of duplications - const totalCount1 = violationMsg1.substr(violationMsg1.indexOf("of "), violationMsg1.indexOf(" duplication")); - const totalCount2 = violationMsg2.substr(violationMsg2.indexOf("of "), violationMsg2.indexOf(" duplication")); - expect(totalCount2).equals(totalCount1); }); describe("Processing Minimum Tokens value", () => { @@ -77,14 +75,15 @@ describe("End to end tests for CPD engine", () => { describe("Pulls value from environment variable if it is...", () => { it("...a wholly numeric string", () => { - process.env[MINIMUM_TOKENS_ENV_VAR] = '50'; + process.env[MINIMUM_TOKENS_ENV_VAR] = '200'; const output = runCommand(`scanner run --target ${Cpd_Test_Code_Path} --engine cpd --format json`); + assertNoError(output) verifyEnvVarIsUsedForMinimumTokens(output.shellOutput); }); it("...a partly numeric string", () => { - // The environment variable processing will strip non-numeric characters, making this "50". - process.env[MINIMUM_TOKENS_ENV_VAR] = 'My5String0'; + // The environment variable processing will strip non-numeric characters, making this "600". + process.env[MINIMUM_TOKENS_ENV_VAR] = 'My2String00'; const output = runCommand(`scanner run --target ${Cpd_Test_Code_Path} --engine cpd --format json`); verifyEnvVarIsUsedForMinimumTokens(output.shellOutput); }); @@ -113,20 +112,20 @@ describe("End to end tests for CPD engine", () => { }); }); function verifyEnvVarIsUsedForMinimumTokens(ctx) { - const Minimum_Tokens_50 = [Apex_File1, Apex_File2, Vf_File1, Vf_File2].sort(); + const Minimum_Tokens_200 = [Vf_File1, Vf_File2].sort(); const ruleResults: RuleResult[] = extractRuleResults(ctx); const actualFileNames = ruleResults.map(ruleResult => ruleResult.fileName).sort(); - expect(ruleResults.length).equals(Minimum_Tokens_50.length); + expect(ruleResults.length).equals(Minimum_Tokens_200.length); for (let i = 0; i < ruleResults.length; i++) { - // Comparing substring since actualFileName contains full path and Minimum_Tokens_50 contains relative paths - expect(actualFileNames[i]).contains(Minimum_Tokens_50[i]); + // Comparing substring since actualFileName contains full path and Minimum_Tokens_300 contains relative paths + expect(actualFileNames[i]).contains(Minimum_Tokens_200[i]); } } function verifyDefaultConfigIsUsedForMinimumTokens(ctx) { - const Minimum_Tokens_100 = [Vf_File1, Vf_File2].sort(); + const Minimum_Tokens_100 = [Apex_File1, Apex_File2, Vf_File1, Vf_File2].sort(); const ruleResults: RuleResult[] = extractRuleResults(ctx); @@ -147,3 +146,9 @@ function extractRuleResults(ctx) { return ruleResults; } +function assertNoError(output) { + if (output.shellOutput.stderr.includes("Error")) { + expect.fail("Found error in stderr output:\n" + output.shellOutput.stderr); + } +} + diff --git a/test/commands/scanner/run.custom.test.ts b/test/commands/scanner/run.custom.test.ts index e1ca67974..6b48a99b4 100644 --- a/test/commands/scanner/run.custom.test.ts +++ b/test/commands/scanner/run.custom.test.ts @@ -13,6 +13,7 @@ describe('scanner run with custom config E2E', () => { it('Can use custom PMD config to detect violations', () => { const targetPath = path.join('.', 'test', 'code-fixtures', 'projects', 'app', 'force-app', 'main', 'default', 'pages', 'testSELECT2.page'); const output = runCommand(`scanner run --target ${targetPath} --pmdconfig ${customPmdConfig} --format json`); + assertNoError(output); const stdout = output.shellOutput.stdout; expect(stdout).to.not.be.empty; @@ -32,6 +33,7 @@ describe('scanner run with custom config E2E', () => { it('Can use custom ESLint config to detect violations', () => { const targetPath = path.join('.', 'test', 'code-fixtures', 'projects', 'ts', 'src', 'simpleYetWrong.ts'); const output = runCommand(`scanner run --target ${targetPath} --eslintconfig ${customEslintConfig} --format json`); + assertNoError(output); const stdout = output.shellOutput.stdout; expect(stdout).to.not.be.empty; @@ -51,6 +53,7 @@ describe('scanner run with custom config E2E', () => { it('Default engine and custom variant are mutually exclusive', () => { const targetPath = path.join('.', 'test', 'code-fixtures', 'projects', 'app', 'force-app'); const output = runCommand(`scanner run --target ${targetPath} --pmdconfig ${customPmdConfig} --format json`); + assertNoError(output); const stdout = output.shellOutput.stdout; const jsonOutput = stdout.slice(stdout.indexOf('['), stdout.lastIndexOf(']') + 1); expect(jsonOutput).to.not.be.empty; @@ -64,3 +67,9 @@ describe('scanner run with custom config E2E', () => { expect(parsedOutput.length).to.equal(onlyCustomPmdAndDefaultEslint.length, 'Violations should be only from Custom PMD and Default ESLint'); }); }); + +function assertNoError(output) { + if (output.shellOutput.stderr.includes("Error")) { + expect.fail("Found error in stderr output:\n" + output.shellOutput.stderr); + } +} diff --git a/test/commands/scanner/run.filters.test.ts b/test/commands/scanner/run.filters.test.ts index 820015565..f66591c0f 100644 --- a/test/commands/scanner/run.filters.test.ts +++ b/test/commands/scanner/run.filters.test.ts @@ -166,7 +166,7 @@ describe('scanner run tests that result in the use of RuleFilters', function () const output = JSON.parse(stdout.slice(stdout.indexOf('['), stdout.lastIndexOf(']') + 1)); expect(output.length).to.equal(1, 'Should only be violations from one file'); expect(output[0].engine).to.equal('pmd'); - expect(output[0].violations, TestUtils.prettyPrint(output[0].violations)).to.be.lengthOf(2); + expect(output[0].violations, TestUtils.prettyPrint(output[0].violations)).to.be.lengthOf(1); // Make sure only violations are returned for the requested category for (const v of output[0].violations) { diff --git a/test/commands/scanner/run.test.ts b/test/commands/scanner/run.test.ts index 8a006a58c..422a493b2 100644 --- a/test/commands/scanner/run.test.ts +++ b/test/commands/scanner/run.test.ts @@ -22,33 +22,19 @@ describe('scanner run', function () { } describe('Test Case: Running rules against a single file', () => { - it('When the file contaisn violations, they are logged out as an XML', () => { - const output = runCommand(`scanner run --target ${pathToSomeTestClass} --ruleset ApexUnit --format xml`); + it('When the file contains violations, they are logged out as an XML', () => { + const output = runCommand(`scanner run --target ${pathToSomeTestClass} --category "Best Practices" --format xml`); + assertNoError(output); validateXmlOutput(output.shellOutput.stdout); }); it('When the file contains no violations, a message is logged to the console', () => { - const output = runCommand(`scanner run --target ${pathToYetAnotherTestClass} --ruleset ApexUnit --format xml`); + const output = runCommand(`scanner run --target ${pathToYetAnotherTestClass} --category "Best Practices" --format xml`); + assertNoError(output); expect(output.shellOutput.stdout).to.contain(getMessage(BundleName.RunOutputProcessor, 'output.noViolationsDetected', ['pmd, retire-js'])); }); }); - describe('Test Case: Running multiple rulesets at once', () => { - it('Violations from each rule are logged as an XML', () => { - const output = runCommand(`scanner run --target ${pathToAnotherTestClass} --ruleset ApexUnit,Style --format xml`); - expect(output.shellOutput.stderr).contains(getMessage(BundleName.Run, 'rulesetDeprecation'), 'Expected ruleset deprecation message'); - // We'll split the output by the tag, so we can get individual violations. - const violations = output.shellOutput.stdout.split(' { const testout = 'testout.xml'; afterEach(() => { @@ -58,7 +44,7 @@ describe('scanner run', function () { }); it('Returned violations are written to file as XML', () => { - runCommand(`scanner run --target ${pathToSomeTestClass} --ruleset ApexUnit --outfile ${testout}`); + runCommand(`scanner run --target ${pathToSomeTestClass} --category "Best Practices" --outfile ${testout}`); // Verify that the file we wanted was actually created. expect(fs.existsSync(testout)).to.equal(true, 'The command should have created the expected output file'); const fileContents = fs.readFileSync(testout).toString(); @@ -66,7 +52,7 @@ describe('scanner run', function () { }); it('Absence of violations yields empty XML file', () => { - runCommand(`scanner run --target ${pathToYetAnotherTestClass} --ruleset ApexUnit --outfile ${testout}`); + runCommand(`scanner run --target ${pathToYetAnotherTestClass} --category "Best Practices" --outfile ${testout}`); // Verify that an empty XML file was actually created. expect(fs.existsSync(testout)).to.equal(true, 'The command should have created an empty output file'); const fileContents = fs.readFileSync(testout).toString(); @@ -89,16 +75,15 @@ describe('scanner run', function () { const rows = csv.trim().split('\n'); rows.shift(); - // There should be two rows. - expect(rows.length).to.equal(2, 'Should be two violations detected'); + // There should be at least two rows. + expect(rows.length).to.be.greaterThanOrEqual(2, 'Should be two or more violations detected'); - // Split each row by commas, so we'll have each cell. - const data = rows.map(val => val.split(',')); - // Verify that each row looks approximately right. - expect(data[0][3]).to.equal('"11"', 'Violation #1 should occur on the expected line'); - expect(data[1][3]).to.equal('"19"', 'Violation #2 should occur on the expected line'); - expect(data[0][5]).to.equal('"ApexUnitTestClassShouldHaveAsserts"', 'Violation #1 should be of the expected type'); - expect(data[1][5]).to.equal('"ApexUnitTestClassShouldHaveAsserts"', 'Violation #2 should be of the expected type'); + let numMatches = 0 + for (const row of rows) { + const data = row.split(',') + numMatches += (data[3] == `"11"` || data[3] == `"19"`) && data[5] == `"ApexUnitTestClassShouldHaveAsserts"` ? 1 : 0; + } + expect(numMatches).to.equal(2, `Should have violations of ApexUnitTestClassShouldHaveAsserts at line 11 and at line 19.`); } function validateNoViolationsCsvOutput(contents: string, expectSummary=true): void { @@ -124,15 +109,16 @@ describe('scanner run', function () { }); it('Properly writes CSV to console', () => { - const output = runCommand(`scanner run --target ${pathToSomeTestClass} --ruleset ApexUnit --format csv`); - // Split the output by newline characters and throw away the first entry, so we're left with just the rows. + const output = runCommand(`scanner run --target ${pathToSomeTestClass} --category "Best Practices" --format csv`); + assertNoError(output); validateCsvOutput(output.shellOutput.stdout, false); }); it('Properly writes CSV to file', () => { - const output = runCommand(`scanner run --target ${pathToSomeTestClass} --ruleset ApexUnit --outfile ${testout}`); + const output = runCommand(`scanner run --target ${pathToSomeTestClass} --category "Best Practices" --outfile ${testout}`); + assertNoError(output); // Verify that the correct message is displayed to user - expect(output.shellOutput.stdout).to.contain(getMessage(BundleName.RunOutputProcessor, 'output.engineSummaryTemplate', ['pmd', 2, 1]), 'Expected summary to be correct'); + expect(output.shellOutput.stdout).to.contain(getMessage(BundleName.RunOutputProcessor, 'output.engineSummaryTemplate', ['pmd', 7, 1]), 'Expected summary to be correct'); expect(output.shellOutput.stdout).to.contain(getMessage(BundleName.RunOutputProcessor, 'output.writtenToOutFile', [testout])); // Verify that the file we wanted was actually created. @@ -142,12 +128,14 @@ describe('scanner run', function () { }); it('When no violations are detected, a message is logged to the console', () => { - const output = runCommand(`scanner run --target ${pathToYetAnotherTestClass} --ruleset ApexUnit --format csv`); + const output = runCommand(`scanner run --target ${pathToYetAnotherTestClass} --category "Best Practices" --format csv`); + assertNoError(output); expect(output.shellOutput.stdout).to.contain(getMessage(BundleName.RunOutputProcessor, 'output.noViolationsDetected', ['pmd, retire-js'])); }); it('When --outfile is provided and no violations are detected, CSV file with no violations is created', () => { - const output = runCommand(`scanner run --target ${pathToYetAnotherTestClass} --ruleset ApexUnit --outfile ${testout}`); + const output = runCommand(`scanner run --target ${pathToYetAnotherTestClass} --category "Best Practices" --outfile ${testout}`); + assertNoError(output); expect(output.shellOutput.stdout).to.contain(getMessage(BundleName.RunOutputProcessor, 'output.writtenToOutFile', [testout])); const fileContents = fs.readFileSync(testout).toString(); @@ -164,13 +152,13 @@ describe('scanner run', function () { expect(result[1]).to.be.not.null; const rows = JSON.parse(result[1]); - expect(rows.length).to.equal(2); + expect(rows.length).to.be.greaterThanOrEqual(2); - // Verify that each row looks approximately right. - expect(rows[0]['line']).to.equal(11, 'Violation #1 should occur on the expected line'); - expect(rows[1]['line']).to.equal(19, 'Violation #2 should occur on the expected line'); - expect(rows[0]['ruleName']).to.equal('ApexUnitTestClassShouldHaveAsserts', 'Violation #1 should be of the expected type'); - expect(rows[1]['ruleName']).to.equal('ApexUnitTestClassShouldHaveAsserts', 'Violation #2 should be of the expected type'); + let numMatches = 0 + for (const row of rows) { + numMatches += (row.line == 11 || row.line == 19) && row.ruleName == "ApexUnitTestClassShouldHaveAsserts" ? 1 : 0; + } + expect(numMatches).to.equal(2, `Should have violations of ApexUnitTestClassShouldHaveAsserts at line 11 and at line 19.`); } function validateNoViolationsHtmlOutput(html: string): void { @@ -189,14 +177,14 @@ describe('scanner run', function () { }); it('Properly writes HTML to console', () => { - const output = runCommand(`scanner run --target ${pathToSomeTestClass} --ruleset ApexUnit --format html`); - // Parse out the HTML results + const output = runCommand(`scanner run --target ${pathToSomeTestClass} --category "Best Practices" --format html`); + assertNoError(output); validateHtmlOutput(output.shellOutput.stdout); }); it('Properly writes HTML to file', () => { - const output = runCommand(`scanner run --target ${pathToSomeTestClass} --ruleset ApexUnit --outfile ${outputFile}`); - // Verify that the correct message is displayed to user + const output = runCommand(`scanner run --target ${pathToSomeTestClass} --category "Best Practices" --outfile ${outputFile}`); + assertNoError(output); expect(output.shellOutput.stdout).to.contain(getMessage(BundleName.RunOutputProcessor, 'output.writtenToOutFile', [outputFile])); // Verify that the file we wanted was actually created. @@ -206,12 +194,14 @@ describe('scanner run', function () { }); it('When no violations are detected, a message is logged to the console', () => { - const output = runCommand(`scanner run --target ${pathToYetAnotherTestClass} --ruleset ApexUnit --format html`); + const output = runCommand(`scanner run --target ${pathToYetAnotherTestClass} --category "Best Practices" --format html`); + assertNoError(output); expect(output.shellOutput.stdout).to.contain(getMessage(BundleName.RunOutputProcessor, 'output.noViolationsDetected', ['pmd, retire-js'])); }); it('When --outfile is provided and no violations are detected, HTML file with no violations should be created', () => { - const output = runCommand(`scanner run --target ${pathToYetAnotherTestClass} --ruleset ApexUnit --outfile ${outputFile}`); + const output = runCommand(`scanner run --target ${pathToYetAnotherTestClass} --category "Best Practices" --outfile ${outputFile}`); + assertNoError(output); expect(output.shellOutput.stdout).to.contain(getMessage(BundleName.RunOutputProcessor, 'output.writtenToOutFile', [outputFile])); expect(fs.existsSync(outputFile)).to.be.true; const fileContents = fs.readFileSync(outputFile).toString(); @@ -234,15 +224,17 @@ describe('scanner run', function () { }); it('Properly writes JSON to console', () => { - const output = runCommand(`scanner run --target ${pathToSomeTestClass} --ruleset ApexUnit --format json`); + const output = runCommand(`scanner run --target ${pathToSomeTestClass} --category "Best Practices" --format json`); + assertNoError(output); const stdout = output.shellOutput.stdout; validateJsonOutput(stdout.slice(stdout.indexOf('['), stdout.lastIndexOf(']') + 1)); }); it('Properly writes JSON to file', () => { - const output = runCommand(`scanner run --target ${pathToSomeTestClass} --ruleset ApexUnit --outfile ${testout}`); + const output = runCommand(`scanner run --target ${pathToSomeTestClass} --category "Best Practices" --outfile ${testout}`); + assertNoError(output); // Verify that the correct message is displayed to user - expect(output.shellOutput.stdout).to.contain(getMessage(BundleName.RunOutputProcessor, 'output.engineSummaryTemplate', ['pmd', 2, 1]), 'Expected summary to be correct'); + expect(output.shellOutput.stdout).to.contain(getMessage(BundleName.RunOutputProcessor, 'output.engineSummaryTemplate', ['pmd', 7, 1]), 'Expected summary to be correct'); expect(output.shellOutput.stdout).to.contain(getMessage(BundleName.RunOutputProcessor, 'output.writtenToOutFile', [testout])); // Verify that the file we wanted was actually created. @@ -252,12 +244,14 @@ describe('scanner run', function () { }); it('When no violations are detected, a message is logged to the console', () => { - const output = runCommand(`scanner run --target ${pathToYetAnotherTestClass} --ruleset ApexUnit --format json`); + const output = runCommand(`scanner run --target ${pathToYetAnotherTestClass} --category "Best Practices" --format json`); + assertNoError(output); expect(output.shellOutput.stdout).to.contain(getMessage(BundleName.RunOutputProcessor, 'output.noViolationsDetected', ['pmd, retire-js'])); }); it('When --outfile is provided and no violations are detected, a JSON file with no violations is created', () => { - const output = runCommand(`scanner run --target ${pathToYetAnotherTestClass} --ruleset ApexUnit --outfile ${testout}`); + const output = runCommand(`scanner run --target ${pathToYetAnotherTestClass} --category "Best Practices" --outfile ${testout}`); + assertNoError(output); expect(output.shellOutput.stdout).to.contain(getMessage(BundleName.RunOutputProcessor, 'output.writtenToOutFile', [testout])); expect(fs.existsSync(testout)).to.be.true; const fileContents = fs.readFileSync(testout).toString(); @@ -269,24 +263,16 @@ describe('scanner run', function () { describe('Output Type: Table', () => { // The table can't be written to a file, so we're just testing the console. it('Properly writes table to the console', () => { - const output = runCommand(`scanner run --target ${pathToSomeTestClass} --ruleset ApexUnit --format table`); - // Split the output by newline characters and throw away the first two rows, which are the column names and a separator. - // That will leave us with just the rows. - const rows = output.shellOutput.stdout.trim().split('\n'); - - // Assert rows have the right error on the right line. - expect(rows.find(r => r.indexOf("SomeTestClass.cls:11") > 0)).to.contain('Apex unit tests should System.assert()'); - expect(rows.find(r => r.indexOf("SomeTestClass.cls:19") > 0)).to.contain('Apex unit tests should System.assert()'); + const output = runCommand(`scanner run --target ${pathToSomeTestClass} --category "Best Practices" --format table`); + assertNoError(output); + expect(output.shellOutput.stdout).to.match(/SomeTestClass.cls:11[^\n]+Apex unit tests should System\.assert/) + expect(output.shellOutput.stdout).to.match(/SomeTestClass.cls:19[^\n]+Apex unit tests should System\.assert/) }); it('When no violations are detected, an empty table is logged to the console', () => { - const output = runCommand(`scanner run --target ${pathToYetAnotherTestClass} --ruleset ApexUnit --format table`); - // Split the output by newline characters and throw away the first two rows, which are the column names and a separator. - // That will leave us with just the rows. - const rows = output.shellOutput.stdout.trim().split('\n'); - - // Expect to find no violations listing this class. - expect(rows.find(r => r.indexOf("SomeTestClass.cls") > 0)).to.equal(undefined, "more rows??"); + const output = runCommand(`scanner run --target ${pathToYetAnotherTestClass} --category "Best Practices" --format table`); + assertNoError(output); + expect(output.shellOutput.stdout).to.not.contain("SomeTestClass.cls") }); }); @@ -299,37 +285,24 @@ describe('scanner run', function () { }); it('--json flag uses default format of JSON', () => { - const commandOutput = runCommand(`scanner run --target ${pathToSomeTestClass} --ruleset ApexUnit --json`) + const commandOutput = runCommand(`scanner run --target ${pathToSomeTestClass} --category "Best Practices" --json`); + assertNoError(commandOutput); const output = JSON.parse(commandOutput.shellOutput.stdout); expect(output.status).to.equal(0, 'Should have finished properly'); - const result = output.result; - // Only PMD rules should have run. - expect(result.length).to.equal(1, 'Should only be violations from one engine'); - expect(result[0].engine).to.equal('pmd', 'Engine should be PMD'); - - expect(result[0].violations.length).to.equal(2, 'Should be 2 violations'); - expect(result[0].violations[0].line).to.equal(11, 'Violation #1 should occur on the expected line'); - expect(result[0].violations[1].line).to.equal(19, 'Violation #2 should occur on the expected line'); + validateJsonOutput(JSON.stringify(output.result)) }); it('--json flag wraps other formats in a string', () => { - const commandOutput = runCommand(`scanner run --target ${pathToSomeTestClass} --ruleset ApexUnit --format xml --json`); + const commandOutput = runCommand(`scanner run --target ${pathToSomeTestClass} --category "Best Practices" --format xml --json`); + assertNoError(commandOutput); const output = JSON.parse(commandOutput.shellOutput.stdout); expect(output.status).to.equal(0, 'Should have finished properly'); - // We'll split the output by the tag, so we can get individual violations. - const violations = output.result.split(' { - const commandOutput = runCommand(`scanner run --target ${pathToSomeTestClass} --ruleset ApexUnit --outfile ${testout} --json`); + const commandOutput = runCommand(`scanner run --target ${pathToSomeTestClass} --category "Best Practices" --outfile ${testout} --json`); + assertNoError(commandOutput); const output = JSON.parse(commandOutput.shellOutput.stdout); expect(output.status).to.equal(0, 'Should finish properly'); const result = output.result; @@ -337,19 +310,12 @@ describe('scanner run', function () { // Verify that the file we wanted was actually created. expect(fs.existsSync(testout)).to.equal(true, 'The command should have created the expected output file'); const fileContents = fs.readFileSync(testout).toString(); - // We'll split the output by the tag, so we can get individual violations. - const violations = fileContents.split(' { - const commandOutput = runCommand(`scanner run --target ${pathToYetAnotherTestClass} --ruleset ApexUnit --json`); + const commandOutput = runCommand(`scanner run --target ${pathToYetAnotherTestClass} --category "Best Practices" --json`); + assertNoError(commandOutput); const output = JSON.parse(commandOutput.shellOutput.stdout); expect(output.status).to.equal(0, 'Should have finished properly'); expect(output.result).to.contain(getMessage(BundleName.RunOutputProcessor, 'output.noViolationsDetected', ['pmd, retire-js'])); @@ -359,22 +325,17 @@ describe('scanner run', function () { describe('Edge Cases', () => { describe('Test case: No output specified', () => { it('When no format is specified, we default to a TABLE', () => { - const output = runCommand(`scanner run --target ${pathToSomeTestClass} --ruleset ApexUnit`); - // Split the output by newline characters and throw away the first two rows, which are the column names and a separator. - // That will leave us with just the rows. - const rows = output.shellOutput.stdout.trim().split('\n'); - rows.shift(); - rows.shift(); - - // Assert rows have the right error on the right line. - expect(rows.find(r => r.indexOf("SomeTestClass.cls:11") > 0)).to.contain('Apex unit tests should System.assert()'); - expect(rows.find(r => r.indexOf("SomeTestClass.cls:19") > 0)).to.contain('Apex unit tests should System.assert()'); + const output = runCommand(`scanner run --target ${pathToSomeTestClass} --category "Best Practices"`); + assertNoError(output); + expect(output.shellOutput.stdout).to.match(/SomeTestClass.cls:11[^\n]+Apex unit tests should System\.assert/) + expect(output.shellOutput.stdout).to.match(/SomeTestClass.cls:19[^\n]+Apex unit tests should System\.assert/) }) }); describe('Test Case: No rules specified', () => { it('When no rules are explicitly specified, all rules are run', () => { const output = runCommand(`scanner run --target ${pathToAnotherTestClass} --format xml`); + assertNoError(output); // We'll split the output by the tag, so we can get individual violations. const violations = output.shellOutput.stdout.split(' { - const output = runCommand(`scanner run --target path/that/does/notmatter --ruleset ApexUnit --outfile NotAValidFileName`); + const output = runCommand(`scanner run --target path/that/does/notmatter --category "Best Practices" --outfile NotAValidFileName`); expect(output.shellOutput.stderr).to.contain(`Error (1): ${getMessage(BundleName.CommonRun, 'validations.outfileMustBeValid')}`); }); it('Error thrown when output file is unsupported type', () => { - const output = runCommand(`scanner run --target path/that/does/not/matter --ruleset ApexUnit --outfile badtype.pdf`); + const output = runCommand(`scanner run --target path/that/does/not/matter --category "Best Practices" --outfile badtype.pdf`); expect(output.shellOutput.stderr).to.contain(`Error (1): ${getMessage(BundleName.CommonRun, 'validations.outfileMustBeSupportedType')}`); }) @@ -419,6 +380,7 @@ describe('scanner run', function () { const allJsGlob = path.join(pathToApp, '**', '*.js'); const allApexGlob = path.join(pathToApp, '**', '*.cls'); const output = runCommand(`scanner run --target "${allJsGlob},${allApexGlob}" --format json`); + assertNoError(output); const results = JSON.parse(output.shellOutput.stdout.substring(output.shellOutput.stdout.indexOf("[{"), output.shellOutput.stdout.lastIndexOf("}]") + 2)); // Look through all of the results and gather a set of unique engines const uniqueEngines = new Set(results.map(r => { return r.engine })); @@ -436,6 +398,7 @@ describe('scanner run', function () { describe('BaseConfig Environment Tests For Javascript', () => { it('The baseConfig enables the usage of default Javascript Types', () => { const output = runCommand(`scanner run --target ${path.join('test', 'code-fixtures', 'projects', 'js', 'src', 'baseConfigEnv.js')} --format csv`); + assertNoError(output); // There should be no violations. expect(output.shellOutput.stdout).to.contain(getMessage(BundleName.RunOutputProcessor, 'output.noViolationsDetected', ['pmd, eslint, retire-js'])); }); @@ -444,6 +407,7 @@ describe('scanner run', function () { // DON'T BE AFRAID TO CHANGE/DELETE THIS TEST AT THAT POINT. it('By default, frameworks such as QUnit are not included in the baseConfig', () => { const output = runCommand(`scanner run --target ${path.join('test', 'code-fixtures', 'projects', 'js', 'src', 'fileThatUsesQUnit.js')} --format json`); + assertNoError(output); // We expect there to be 2 errors about qunit-related syntax being undefined. const stdout = output.shellOutput.stdout; const parsedCtx = JSON.parse(stdout.slice(stdout.indexOf('['), stdout.lastIndexOf(']') + 1)); @@ -455,6 +419,7 @@ describe('scanner run', function () { // DON'T BE AFRAID TO CHANGE/DELETE THIS TEST AT THAT POINT. it('Providing qunit in the --env override should resolve errors about that framework', () => { const output = runCommand(`scanner run --target ${path.join('test', 'code-fixtures', 'projects', 'js', 'src', 'fileThatUsesQUnit.js')} --format json --env "{\\"qunit\\": true}"`); + assertNoError(output); expect(output.shellOutput.stdout).to.contain(getMessage(BundleName.RunOutputProcessor, 'output.noViolationsDetected', ['pmd, eslint, retire-js'])); }); }); @@ -462,6 +427,7 @@ describe('scanner run', function () { describe('run with format --json', () => { it('provides only json in stdout', () => { const output = runCommand(`scanner run --target ${pathToAnotherTestClass} --format json`); + assertNoError(output); try { JSON.parse(output.shellOutput.stdout); } catch (error) { @@ -489,6 +455,7 @@ describe('scanner run', function () { describe('Verbose tests must come last. Verbose does not reset', () => { it('When the --verbose flag is supplied, info about implicitly run rules is logged', () => { const output = runCommand(`scanner run --target ${pathToYetAnotherTestClass} --format xml --verbose`); + assertNoError(output); // We'll split the output by the tag, so we can get individual violations. const violations = output.shellOutput.stdout.split(' { const internalOutfile = path.join(tmpDir, "internalOutfile.json"); process.env[ENV_VAR_NAMES.SCANNER_INTERNAL_OUTFILE] = internalOutfile; - const output = runCommand(`scanner run --target ${pathToSomeTestClass} --ruleset ApexUnit --format xml`); - + const output = runCommand(`scanner run --target ${pathToSomeTestClass} --category "Best Practices" --format xml`); + assertNoError(output); validateXmlOutput(output.shellOutput.stdout); expect(fs.existsSync(internalOutfile)).to.equal(true, 'The command should have created the expected internal output file'); @@ -550,7 +517,7 @@ describe('scanner run', function () { const internalOutfile = path.join(tmpDir, "internalOutfile.notSupported"); process.env[ENV_VAR_NAMES.SCANNER_INTERNAL_OUTFILE] = internalOutfile; const userOutfile = path.join(tmpDir, "userOutfile.xml"); - const output = runCommand(`scanner run --target ${pathToSomeTestClass} --ruleset ApexUnit --outfile ${userOutfile}`); + const output = runCommand(`scanner run --target ${pathToSomeTestClass} --category "Best Practices" --outfile ${userOutfile}`); expect(output.shellOutput.stderr).contains( getMessage(BundleName.CommonRun, 'internal.outfileMustBeSupportedType', [ENV_VAR_NAMES.SCANNER_INTERNAL_OUTFILE])); @@ -559,16 +526,9 @@ describe('scanner run', function () { }); function validateXmlOutput(xml: string): void { - // We'll split the output by the tag, so we can get individual violations. - const violations = xml.split(' Date: Fri, 22 Mar 2024 16:46:02 -0400 Subject: [PATCH 10/10] Update our default catalog fixture for testing to reflect PMD7 changes --- .../DefaultCatalogFixture.json | 16 +------ test/lib/services/LocalCatalog.test.ts | 43 ++++++------------- 2 files changed, 14 insertions(+), 45 deletions(-) diff --git a/test/catalog-fixtures/DefaultCatalogFixture.json b/test/catalog-fixtures/DefaultCatalogFixture.json index 39b262bbe..a79987531 100644 --- a/test/catalog-fixtures/DefaultCatalogFixture.json +++ b/test/catalog-fixtures/DefaultCatalogFixture.json @@ -2,7 +2,6 @@ "categories": [ { "paths": [ - "category/ecmascript/design.xml", "category/apex/design.xml" ], "name": "Design", @@ -10,7 +9,6 @@ }, { "paths": [ - "category/ecmascript/bestpractices.xml", "category/apex/bestpractices.xml" ], "name": "Best Practices", @@ -18,7 +16,6 @@ }, { "paths": [ - "category/ecmascript/errorprone.xml", "category/apex/errorprone.xml" ], "name": "Error Prone", @@ -63,18 +60,9 @@ "rulesets": [ { "paths": [ - "rulesets/ecmascript/braces.xml", - "rulesets/apex/braces.xml" + "rulesets/apex/quickstart.xml" ], - "name": "Braces", - "engine": "pmd" - }, - { - "paths": [ - "rulesets/apex/security.xml", - "rulesets/vf/security.xml" - ], - "name": "Security", + "name": "quickstart", "engine": "pmd" } ], diff --git a/test/lib/services/LocalCatalog.test.ts b/test/lib/services/LocalCatalog.test.ts index 3392105dd..e350e13c3 100644 --- a/test/lib/services/LocalCatalog.test.ts +++ b/test/lib/services/LocalCatalog.test.ts @@ -27,8 +27,6 @@ describe('LocalCatalog', () => { describe('getRuleGroupsMatchingFilters', async () => { const ENGINES = await Controller.getAllEngines(); - - const LANGUAGE_ECMASCRIPT = 'ecmascript'; /** * Return a map of key=:, value=RuleGroup */ @@ -100,34 +98,17 @@ describe('LocalCatalog', () => { it('Correctly filters by one value', async () => { // SETUP // Create a filter that matches a single ruleset. - const filter: RuleFilter = new RulesetFilter(['Braces']); + const filter: RuleFilter = new RulesetFilter(['quickstart']); // INVOCATION OF TESTED METHOD // Use the created filter to filter the available rules. const ruleGroups: RuleGroup[] = await catalog.getRuleGroupsMatchingFilters([filter], ENGINES); // ASSERTIONS - // We expect a single ruleset, corresponding to PMD's "Braces" ruleset. + // We expect a single ruleset, corresponding to PMD's "quickstart" ruleset. expect(ruleGroups, TestUtils.prettyPrint(ruleGroups)).to.be.lengthOf(1); const mappedRuleGroups = mapRuleGroups(ruleGroups); - validatePmdRuleset(mappedRuleGroups, 'Braces', [LANGUAGE_ECMASCRIPT, LANGUAGE.APEX]); - }); - - it('Correctly filters by multiple values', async () => { - // SETUP - // Create a filter that matches two rulesets. - const filter: RuleFilter = new RulesetFilter(['Security', 'Braces']); - - // INVOCATION OF TESTED METHOD - // Use the created filter to filter the available rules. - const ruleGroups: RuleGroup[] = await catalog.getRuleGroupsMatchingFilters([filter], ENGINES); - - // ASSERTIONS - // We expect two rulesets, corresponding to PMD's "Security" and "Braces" rulesets. - expect(ruleGroups, TestUtils.prettyPrint(ruleGroups)).to.be.lengthOf(2); - const mappedRuleGroups = mapRuleGroups(ruleGroups); - validatePmdRuleset(mappedRuleGroups, 'Braces', [LANGUAGE_ECMASCRIPT, LANGUAGE.APEX]); - validatePmdRuleset(mappedRuleGroups, 'Security', ['apex', 'vf']); + validatePmdRuleset(mappedRuleGroups, 'quickstart', [LANGUAGE.APEX]); }); }); @@ -148,7 +129,7 @@ describe('LocalCatalog', () => { const mappedRuleGroups = mapRuleGroups(ruleGroups); validateEslintBestPractices(mappedRuleGroups); - validatePmdCategory(mappedRuleGroups, 'Best Practices', [LANGUAGE_ECMASCRIPT, LANGUAGE.APEX]); + validatePmdCategory(mappedRuleGroups, 'Best Practices', [LANGUAGE.APEX]); }); it('Correctly filters by multiple values', async () => { @@ -166,7 +147,7 @@ describe('LocalCatalog', () => { const mappedRuleGroups = mapRuleGroups(ruleGroups); validateEslintPossibleErrors(mappedRuleGroups); validateEslintBestPractices(mappedRuleGroups); - validatePmdCategory(mappedRuleGroups, 'Best Practices', [LANGUAGE.APEX, LANGUAGE_ECMASCRIPT]); + validatePmdCategory(mappedRuleGroups, 'Best Practices', [LANGUAGE.APEX]); }); }); @@ -186,8 +167,8 @@ describe('LocalCatalog', () => { const mappedRuleGroups = mapRuleGroups(ruleGroups); validateEslintPossibleErrors(mappedRuleGroups); - validatePmdCategory(mappedRuleGroups, 'Design', [LANGUAGE.APEX, LANGUAGE_ECMASCRIPT]); - validatePmdCategory(mappedRuleGroups, 'Error Prone', [LANGUAGE.APEX, LANGUAGE_ECMASCRIPT]); + validatePmdCategory(mappedRuleGroups, 'Design', [LANGUAGE.APEX]); + validatePmdCategory(mappedRuleGroups, 'Error Prone', [LANGUAGE.APEX]); validateCpdCategory(mappedRuleGroups, 'Copy/Paste Detected', [LANGUAGE.APEX, LANGUAGE.JAVA, LANGUAGE.VISUALFORCE, LANGUAGE.XML]); }); @@ -206,7 +187,7 @@ describe('LocalCatalog', () => { const mappedRuleGroups = mapRuleGroups(ruleGroups); validateEslintPossibleErrors(mappedRuleGroups); - validatePmdCategory(mappedRuleGroups, 'Error Prone', [LANGUAGE.APEX, LANGUAGE_ECMASCRIPT]); + validatePmdCategory(mappedRuleGroups, 'Error Prone', [LANGUAGE.APEX]); validateCpdCategory(mappedRuleGroups, 'Copy/Paste Detected', [LANGUAGE.APEX, LANGUAGE.JAVA, LANGUAGE.VISUALFORCE, LANGUAGE.XML]); }); }); @@ -230,7 +211,7 @@ describe('LocalCatalog', () => { const mappedRuleGroups = mapRuleGroups(ruleGroups); validateEslintBestPractices(mappedRuleGroups); - validatePmdCategory(mappedRuleGroups, 'Best Practices', [LANGUAGE_ECMASCRIPT, LANGUAGE.APEX]); + validatePmdCategory(mappedRuleGroups, 'Best Practices', [LANGUAGE.APEX]); }); }); @@ -241,9 +222,9 @@ describe('LocalCatalog', () => { validateEslintBestPractices(mappedRuleGroups); validateEslintPossibleErrors(mappedRuleGroups); - validatePmdCategory(mappedRuleGroups, 'Best Practices', [LANGUAGE_ECMASCRIPT, LANGUAGE.APEX]); - validatePmdCategory(mappedRuleGroups, 'Design', [LANGUAGE_ECMASCRIPT, LANGUAGE.APEX]); - validatePmdCategory(mappedRuleGroups, 'Error Prone', [LANGUAGE_ECMASCRIPT, LANGUAGE.APEX]); + validatePmdCategory(mappedRuleGroups, 'Best Practices', [LANGUAGE.APEX]); + validatePmdCategory(mappedRuleGroups, 'Design', [LANGUAGE.APEX]); + validatePmdCategory(mappedRuleGroups, 'Error Prone', [LANGUAGE.APEX]); validateCpdCategory(mappedRuleGroups, 'Copy/Paste Detected', [LANGUAGE.APEX, LANGUAGE.JAVA, LANGUAGE.VISUALFORCE, LANGUAGE.XML]); }