From 6337ada7d17109532b58cfe7a97c868804ed3c0f Mon Sep 17 00:00:00 2001 From: Josh Feingold Date: Wed, 13 Dec 2023 11:51:44 -0600 Subject: [PATCH 01/10] @W-13222948@: Pmd Catalog generated as temp file. --- src/Constants.ts | 1 - src/lib/pmd/PmdCatalogWrapper.ts | 16 +++++----------- src/lib/pmd/PmdEngine.ts | 4 ++-- test/lib/pmd/PmdCatalogWrapper.test.ts | 4 ++-- 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/Constants.ts b/src/Constants.ts index b80f85d3f..2a6957232 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -8,7 +8,6 @@ export const CATALOG_FILE = 'Catalog.json'; export const CUSTOM_PATHS_FILE = 'CustomPaths.json'; export const CONFIG_PILOT_FILE = 'Config-pilot.json'; export const CONFIG_FILE = 'Config.json'; -export const PMD_CATALOG_FILE = 'PmdCatalog.json'; // TODO: We should flesh this one-off solution out into one that handles all the various env vars we use. // E.g., the ones defined in `EnvironmentVariable.ts` and `dfa.ts`. diff --git a/src/lib/pmd/PmdCatalogWrapper.ts b/src/lib/pmd/PmdCatalogWrapper.ts index de213ef73..7bed06ecf 100644 --- a/src/lib/pmd/PmdCatalogWrapper.ts +++ b/src/lib/pmd/PmdCatalogWrapper.ts @@ -9,8 +9,7 @@ import {PmdSupport} from './PmdSupport'; import { PMD_LIB } from '../../Constants'; import path = require('path'); import {uxEvents, EVENTS} from '../ScannerEvents'; -import { Controller } from '../../Controller'; -import { PMD_CATALOG_FILE, PMD_VERSION } from '../../Constants'; +import { PMD_VERSION } from '../../Constants'; import {BundleName, getMessage} from "../../MessageCatalog"; // Here, current dir __dirname = /sfdx-scanner/src/lib/pmd @@ -20,7 +19,7 @@ const MAIN_CLASS = 'sfdc.sfdx.scanner.pmd.Main'; export class PmdCatalogWrapper extends PmdSupport { private logger: Logger; // TODO: add relevant trace logs private initialized: boolean; - private sfdxScannerPath: string; + private catalogFilePath: path.ParsedPath; protected async init(): Promise { if (this.initialized) { @@ -28,8 +27,7 @@ export class PmdCatalogWrapper extends PmdSupport { } await super.init(); this.logger = await Logger.child('PmdCatalogWrapper'); - this.sfdxScannerPath = Controller.getSfdxScannerPath(); - + this.catalogFilePath = path.parse(await new FileHandler().tmpFileWithCleanup()); this.initialized = true; } @@ -39,12 +37,8 @@ export class PmdCatalogWrapper extends PmdSupport { return this.readCatalogFromFile(); } - private getCatalogPath(): string { - return path.join(this.sfdxScannerPath, PMD_CATALOG_FILE); - } - private async readCatalogFromFile(): Promise { - const rawCatalog = await new FileHandler().readFile(this.getCatalogPath()); + const rawCatalog = await new FileHandler().readFile(path.format(this.catalogFilePath)); return JSON.parse(rawCatalog) as Catalog; } @@ -56,7 +50,7 @@ export class PmdCatalogWrapper extends PmdSupport { // is intended for child_process.spawn(), which freaks out if you do that. const classpathEntries = await this.buildClasspath(); const parameters = await this.buildCatalogerParameters(); - const args = [`-DcatalogHome=${this.sfdxScannerPath}`, `-DcatalogName=${PMD_CATALOG_FILE}`, '-cp', classpathEntries.join(path.delimiter), MAIN_CLASS, ...parameters]; + const args = [`-DcatalogHome=${this.catalogFilePath.dir}`, `-DcatalogName=${this.catalogFilePath.base}`, '-cp', classpathEntries.join(path.delimiter), MAIN_CLASS, ...parameters]; this.logger.trace(`Preparing to execute PMD Cataloger with command: "${command}", args: "${JSON.stringify(args)}"`); return [command, args]; diff --git a/src/lib/pmd/PmdEngine.ts b/src/lib/pmd/PmdEngine.ts index 9491b1cf5..1187c6252 100644 --- a/src/lib/pmd/PmdEngine.ts +++ b/src/lib/pmd/PmdEngine.ts @@ -457,8 +457,8 @@ export class PmdEngine extends BasePmdEngine { return await this.runInternal(selectedRules, targets); } - public async isEnabled(): Promise { - return await this.config.isEngineEnabled(PmdEngine.THIS_ENGINE); + public isEnabled(): Promise { + return this.config.isEngineEnabled(PmdEngine.THIS_ENGINE); } } diff --git a/test/lib/pmd/PmdCatalogWrapper.test.ts b/test/lib/pmd/PmdCatalogWrapper.test.ts index 568cd4df6..f584b4154 100644 --- a/test/lib/pmd/PmdCatalogWrapper.test.ts +++ b/test/lib/pmd/PmdCatalogWrapper.test.ts @@ -40,7 +40,7 @@ describe('PmdCatalogWrapper', () => { const thePath = path.join('dist', 'pmd-cataloger', 'lib'); const expectedParamList = [ `-DcatalogHome=`, - '-DcatalogName=PmdCatalog.json', + '-DcatalogName=', '-cp', thePath, 'sfdc.sfdx.scanner.pmd.Main']; @@ -48,7 +48,7 @@ describe('PmdCatalogWrapper', () => { const target = await TestablePmdCatalogWrapper.create({}); const params = (await target.buildCommandArray())[1]; - expectedParamList.forEach((value: string, index: number, array: string[]) => { + expectedParamList.forEach((value: string, index: number) => { expect(params[index]).contains(value, `Unexpected param value at position ${index}`); }); }); From 96ad9046e394b8f41033422236bc050e20706c5c Mon Sep 17 00:00:00 2001 From: Josh Feingold Date: Mon, 18 Dec 2023 13:42:49 -0600 Subject: [PATCH 02/10] @W-13222948@: Refactored PMD support classes. --- src/lib/cpd/CpdWrapper.ts | 6 +- src/lib/pmd/PmdCatalogWrapper.ts | 114 ++++-------------- src/lib/pmd/PmdEngine.ts | 99 +++++++++++++--- src/lib/pmd/PmdSupport.ts | 43 +++---- src/lib/pmd/PmdWrapper.ts | 72 ++++-------- src/lib/services/CommandLineSupport.ts | 4 - src/lib/sfge/SfgeWrapper.ts | 6 +- test/lib/pmd/PmdCatalogWrapper.test.ts | 154 +------------------------ test/lib/pmd/PmdEngine.test.ts | 131 ++++++++++++++++++++- 9 files changed, 284 insertions(+), 345 deletions(-) diff --git a/src/lib/cpd/CpdWrapper.ts b/src/lib/cpd/CpdWrapper.ts index afc8f524c..0b40b5473 100644 --- a/src/lib/cpd/CpdWrapper.ts +++ b/src/lib/cpd/CpdWrapper.ts @@ -32,15 +32,11 @@ export default class CpdWrapper extends CommandLineSupport { this.initialized = true; } - protected buildClasspath(): Promise { - return Promise.resolve([`${PMD_LIB}/*`]); - } - protected async buildCommandArray(): Promise<[string, string[]]> { const javaHome = await JreSetupManager.verifyJreSetup(); const command = path.join(javaHome, 'bin', 'java'); - const classpath = await this.buildClasspath(); + const classpath = [`${PMD_LIB}/*`]; const fileHandler = new FileHandler(); const tmpPath = await fileHandler.tmpFileWithCleanup(); diff --git a/src/lib/pmd/PmdCatalogWrapper.ts b/src/lib/pmd/PmdCatalogWrapper.ts index 7bed06ecf..ccf9756c9 100644 --- a/src/lib/pmd/PmdCatalogWrapper.ts +++ b/src/lib/pmd/PmdCatalogWrapper.ts @@ -1,15 +1,10 @@ import {Logger} from '@salesforce/core'; import {Catalog} from '../../types'; import {FileHandler} from '../util/FileHandler'; -import * as PrettyPrinter from '../util/PrettyPrinter'; import * as JreSetupManager from './../JreSetupManager'; import {ResultHandlerArgs} from '../services/CommandLineSupport'; -import * as PmdLanguageManager from './PmdLanguageManager'; -import {PmdSupport} from './PmdSupport'; -import { PMD_LIB } from '../../Constants'; +import {PmdSupport, PmdSupportOptions} from './PmdSupport'; import path = require('path'); -import {uxEvents, EVENTS} from '../ScannerEvents'; -import { PMD_VERSION } from '../../Constants'; import {BundleName, getMessage} from "../../MessageCatalog"; // Here, current dir __dirname = /sfdx-scanner/src/lib/pmd @@ -17,10 +12,14 @@ const PMD_CATALOGER_LIB = path.join(__dirname, '..', '..', '..', 'dist', 'pmd-ca const MAIN_CLASS = 'sfdc.sfdx.scanner.pmd.Main'; export class PmdCatalogWrapper extends PmdSupport { - private logger: Logger; // TODO: add relevant trace logs + private logger: Logger; private initialized: boolean; private catalogFilePath: path.ParsedPath; + constructor(opts: PmdSupportOptions) { + super(opts); + } + protected async init(): Promise { if (this.initialized) { return; @@ -46,99 +45,34 @@ export class PmdCatalogWrapper extends PmdSupport { const javaHome = await JreSetupManager.verifyJreSetup(); const command = path.join(javaHome, 'bin', 'java'); - // NOTE: If we were going to run this command from the CLI directly, then we'd wrap the classpath in quotes, but this - // is intended for child_process.spawn(), which freaks out if you do that. - const classpathEntries = await this.buildClasspath(); - const parameters = await this.buildCatalogerParameters(); - const args = [`-DcatalogHome=${this.catalogFilePath.dir}`, `-DcatalogName=${this.catalogFilePath.base}`, '-cp', classpathEntries.join(path.delimiter), MAIN_CLASS, ...parameters]; + // The classpath needs the cataloger's lib folder. Note that the classpath is not wrapped in quotes + // like it would be if we invoked through the CLI, because child_process.spawn() hates that. + const classpath: string = [`${PMD_CATALOGER_LIB}/*`, ...this.buildSharedClasspath()].join(path.delimiter); + const languageArgs: string[] = this.buildLanguageArgs(); + this.logger.trace(`Cataloger parameters have been built: ${JSON.stringify(languageArgs)}`); + + const args = [`-DcatalogHome=${this.catalogFilePath.dir}`, `-DcatalogName=${this.catalogFilePath.base}`, '-cp', classpath, MAIN_CLASS, ...languageArgs]; this.logger.trace(`Preparing to execute PMD Cataloger with command: "${command}", args: "${JSON.stringify(args)}"`); return [command, args]; } - protected async buildClasspath(): Promise { - const catalogerLibs = `${PMD_CATALOGER_LIB}/*`; - const classpathEntries = await super.buildSharedClasspath(); - classpathEntries.push(catalogerLibs); - return classpathEntries; - } - - private async buildCatalogerParameters(): Promise { - const pathSetMap = await this.getRulePathEntries(); - const parameters: string[] = []; - const divider = '='; - const joiner = ','; - - // For each language, build an argument that looks like: - // "language=path1,path2,path3" - pathSetMap.forEach((entries, language) => { - const paths = Array.from(entries.values()); - - // add parameter only if paths are available for real - if (paths && paths.length > 0) { - parameters.push(language + divider + paths.join(joiner)); - } - }); - - this.logger.trace(`Cataloger parameters have been built: ${JSON.stringify(parameters)}`); - return parameters; - } - /** - * Return a map where the key is the language and the value is a set of class/jar paths. Start with the given - * default values, if provided. + * Constructs the arguments for the PMD Cataloger so it knows which rules to catalog for which languages. + * @private */ - protected async getRulePathEntries(): Promise>> { - const pathSetMap = new Map>(); - - const customRulePaths: Map> = await this.getCustomRulePathEntries(); - const fileHandler = new FileHandler(); - - - // Iterate through the custom paths. - for (const [langKey, paths] of customRulePaths.entries()) { - // If the language by which these paths are mapped can be de-aliased into one of PMD's default-supported - // languages, we should use the name PMD recognizes. That way, if they have custom paths for 'ecmascript' - // and 'js', we'll turn both of those into 'javascript'. - // If we can't de-alias the key, we should at least convert it to lowercase. Otherwise 'python', 'PYTHON', - // and 'PyThOn' would all be considered different languages. - const lang = (await PmdLanguageManager.resolveLanguageAlias(langKey)) || langKey.toLowerCase(); - this.logger.trace(`Custom paths mapped to ${langKey} are using converted key ${lang}`); + private buildLanguageArgs(): string[] { + const languageArgs: string[] = []; - // Add this language's custom paths to the pathSetMap so they're cataloged properly. - const pathSet = pathSetMap.get(lang) || new Set(); - if (paths) { - for (const value of paths.values()) { - if (await fileHandler.exists(value)) { - pathSet.add(value); - } else { - // The catalog file may have been deleted or moved. Show the user a warning. - uxEvents.emit(EVENTS.WARNING_ALWAYS, getMessage(BundleName.EventKeyTemplates, 'warning.customRuleFileNotFound', [value, lang])); - } - } + // For each language, build an argument that looks like: + // "language=path1,path2,path3" + this.rulePathsByLanguage.forEach((rulePaths, language) => { + if (rulePaths && rulePaths.size > 0) { + languageArgs.push(`${language}=${[...rulePaths].join(',')}`); } - pathSetMap.set(lang, pathSet); - } - - // Now, we'll want to add the default PMD JARs for any activated languages. - const supportedLanguages = await PmdLanguageManager.getSupportedLanguages(); - supportedLanguages.forEach((language) => { - const pmdJarName = PmdCatalogWrapper.derivePmdJarName(language); - const pathSet = pathSetMap.get(language) || new Set(); - pathSet.add(pmdJarName); - this.logger.trace(`Adding JAR ${pmdJarName}, the default PMD JAR for language ${language}`); - pathSetMap.set(language, pathSet); }); - this.logger.trace(`Found PMD rule paths ${PrettyPrinter.stringifyMapOfSets(pathSetMap)}`); - return pathSetMap; - } - - - /** - * PMD library holds the same naming structure for each language - */ - private static derivePmdJarName(language: string): string { - return path.join(PMD_LIB, "pmd-" + language + "-" + PMD_VERSION + ".jar"); + this.logger.trace(`Cataloger parameters have been built: ${JSON.stringify(languageArgs)}`); + return languageArgs; } protected isSuccessfulExitCode(code: number): boolean { diff --git a/src/lib/pmd/PmdEngine.ts b/src/lib/pmd/PmdEngine.ts index 1187c6252..4059011f2 100644 --- a/src/lib/pmd/PmdEngine.ts +++ b/src/lib/pmd/PmdEngine.ts @@ -4,14 +4,18 @@ import {Controller} from '../../Controller'; import {Catalog, Rule, RuleGroup, RuleResult, RuleTarget, RuleViolation, TargetPattern} from '../../types'; import {AbstractRuleEngine} from '../services/RuleEngine'; import {Config} from '../util/Config'; -import {ENGINE, CUSTOM_CONFIG, EngineBase, HARDCODED_RULES, Severity} from '../../Constants'; +import {CUSTOM_CONFIG, ENGINE, EngineBase, HARDCODED_RULES, PMD_LIB, PMD_VERSION, Severity} from '../../Constants'; import {PmdCatalogWrapper} from './PmdCatalogWrapper'; import PmdWrapper from './PmdWrapper'; -import {uxEvents, EVENTS} from "../ScannerEvents"; +import {EVENTS, uxEvents} from "../ScannerEvents"; import {FileHandler} from '../util/FileHandler'; import {EventCreator} from '../util/EventCreator'; import * as engineUtils from '../util/CommonEngineUtils'; import {BundleName, getMessage} from "../../MessageCatalog"; +import * as PrettyPrinter from '../util/PrettyPrinter'; +import * as PmdLanguageManager from './PmdLanguageManager'; +import path = require('path'); +import {AsyncCreatable} from "@salesforce/kit"; interface PmdViolation extends Element { attributes: { @@ -63,7 +67,7 @@ const HARDCODED_RULE_DETAILS: HardcodedRuleDetail[] = [ ]; -abstract class BasePmdEngine extends AbstractRuleEngine { +abstract class AbstractPmdEngine extends AbstractRuleEngine { /** * As per our internal policies, we want to avoid significantly changing output in minor releases, except for * high-severity security rules. @@ -75,7 +79,6 @@ abstract class BasePmdEngine extends AbstractRuleEngine { protected logger: Logger; protected config: Config; - protected pmdCatalogWrapper: PmdCatalogWrapper; protected eventCreator: EventCreator; private initialized: boolean; @@ -126,12 +129,11 @@ abstract class BasePmdEngine extends AbstractRuleEngine { this.logger = await Logger.child(this.getName()); this.config = await Controller.getConfig(); - this.pmdCatalogWrapper = await PmdCatalogWrapper.create({}); this.eventCreator = await EventCreator.create({}); this.initialized = true; } - protected async runInternal(selectedRules: string, targets: RuleTarget[]): Promise { + protected async runInternal(selectedRules: string, targets: RuleTarget[], rulePathsByLanguage: Map>): Promise { try { const targetPaths: string[] = []; for (const target of targets) { @@ -143,8 +145,11 @@ abstract class BasePmdEngine extends AbstractRuleEngine { } this.logger.trace(`About to run PMD rules. Targets: ${targetPaths.length}, Selected rules: ${selectedRules}`); - const selectedTargets = targetPaths.join(','); - const stdout = await PmdWrapper.execute(selectedTargets, selectedRules); + const stdout = await (await PmdWrapper.create({ + targets: targetPaths, + rules: selectedRules, + rulePathsByLanguage + })).execute(); const results = this.processStdOut(stdout); this.logger.trace(`Found ${results.length} for PMD`); return results; @@ -416,16 +421,22 @@ function isCustomRun(engineOptions: Map): boolean { return engineUtils.isCustomRun(CUSTOM_CONFIG.PmdConfig, engineOptions); } -export class PmdEngine extends BasePmdEngine { +export class PmdEngine extends AbstractPmdEngine { private static THIS_ENGINE = ENGINE.PMD; public static ENGINE_NAME = PmdEngine.THIS_ENGINE.valueOf(); + private catalogWrapper: PmdCatalogWrapper; getName(): string { return PmdEngine.ENGINE_NAME; } - getCatalog(): Promise { - return this.pmdCatalogWrapper.getCatalog(); + async getCatalog(): Promise { + if (!this.catalogWrapper) { + this.catalogWrapper = await PmdCatalogWrapper.create({ + rulePathsByLanguage: await (await _PmdRuleMapper.create({})).createStandardRuleMap() + }); + } + return this.catalogWrapper.getCatalog(); } shouldEngineRun( @@ -454,7 +465,7 @@ export class PmdEngine extends BasePmdEngine { this.logger.trace(`${ruleGroups.length} Rules found for PMD engine`); const selectedRules = ruleGroups.map(np => np.paths).join(','); - return await this.runInternal(selectedRules, targets); + return await this.runInternal(selectedRules, targets, await (await _PmdRuleMapper.create({})).createStandardRuleMap()); } public isEnabled(): Promise { @@ -462,7 +473,7 @@ export class PmdEngine extends BasePmdEngine { } } -export class CustomPmdEngine extends BasePmdEngine { +export class CustomPmdEngine extends AbstractPmdEngine { private static THIS_ENGINE = ENGINE.PMD_CUSTOM; getName(): string { @@ -513,7 +524,7 @@ export class CustomPmdEngine extends BasePmdEngine { await this.eventCreator.createUxInfoAlwaysMessage('info.filtersIgnoredCustom', []); } - return await this.runInternal(selectedRules, targets); + return await this.runInternal(selectedRules, targets, await (await _PmdRuleMapper.create({})).createStandardRuleMap()); } private async getCustomConfig(engineOptions: Map): Promise { @@ -528,4 +539,64 @@ export class CustomPmdEngine extends BasePmdEngine { } +/** + * Helper class for PMD variants that need to catalog PMD's default rules and any user-registered rules. + */ +export class _PmdRuleMapper extends AsyncCreatable { + private initialized: boolean; + private logger: Logger; + + public async init(): Promise { + if (this.initialized) { + return; + } + + this.logger = await Logger.child(this.constructor.name); + } + /** + * Creates a mapping from language names to sets of files that define PMD rules for those languages. + * The mapped files encompass PMD's default rules for activated languages, as well as any user-registered + * custom rules. + */ + public async createStandardRuleMap(): Promise>> { + const rulePathsByLanguage = new Map>(); + // Add the default PMD jar for each activated language. + (await PmdLanguageManager.getSupportedLanguages()).forEach(language => { + const pmdJarPath = path.join(PMD_LIB, `pmd-${language}-${PMD_VERSION}.jar`); + const rulePaths = rulePathsByLanguage.get(language) || new Set(); + rulePaths.add(pmdJarPath); + this.logger.trace(`Adding JAR ${pmdJarPath}, the default PMD JAR for language ${language}`); + rulePathsByLanguage.set(language, rulePaths); + }); + + // Next, handle custom paths. + const fileHandler = new FileHandler(); + const customRulePaths: Map> = (await Controller.createRulePathManager()).getRulePathEntries(PmdEngine.ENGINE_NAME); + for (const [languageKey, rulePathSet] of customRulePaths.entries()) { + // Attempt to de-alias the language key into one that PMD will recognize. + // If that doesn't work, just cast the key to lowercase. + const finalKey = (await PmdLanguageManager.resolveLanguageAlias(languageKey)) || languageKey.toLowerCase(); + this.logger.trace(`Custom path language key ${languageKey} converted to cataloger language key ${finalKey}`); + + // Next we want to do an existence check on each of the custom paths. + const mappedPathSet = rulePathsByLanguage.get(finalKey) || new Set(); + if (rulePathSet) { + for (const rulePath of rulePathSet.values()) { + if (await fileHandler.exists(rulePath)) { + // If the path matches an existing file/directory, add it to the map + // and the classpath. + mappedPathSet.add(rulePath); + } else { + // If the path doesn't match an existing file, then the file may have been + // deleted or moved. Warn the user about that. + uxEvents.emit(EVENTS.WARNING_ALWAYS, getMessage(BundleName.EventKeyTemplates, 'warning.customRuleFileNotFound', [rulePath, finalKey])); + } + } + } + rulePathsByLanguage.set(finalKey, mappedPathSet); + } + this.logger.trace(`Found PMD rule paths ${PrettyPrinter.stringifyMapOfSets(rulePathsByLanguage)}`); + return rulePathsByLanguage; + } +} diff --git a/src/lib/pmd/PmdSupport.ts b/src/lib/pmd/PmdSupport.ts index 93b8f4a20..d4cc20702 100644 --- a/src/lib/pmd/PmdSupport.ts +++ b/src/lib/pmd/PmdSupport.ts @@ -1,35 +1,26 @@ -import {Controller} from '../../Controller'; -import {PmdEngine} from './PmdEngine'; import { CommandLineSupport } from '../services/CommandLineSupport'; -import { PMD_LIB } from '../../Constants'; -/** - * Output format supported by PMD - */ -export enum Format { - XML = 'xml', - CSV = 'csv', - TEXT = 'text' -} +export type PmdSupportOptions = { + /** + * A mapping from language names to Sets of files that define rules for those languages. + */ + rulePathsByLanguage: Map>; +}; export abstract class PmdSupport extends CommandLineSupport { + protected readonly rulePathsByLanguage: Map>; - protected async buildSharedClasspath(): Promise { - // Include PMD libs into classpath - const pmdLibs = `${PMD_LIB}/*`; - const classpathEntries = [pmdLibs]; - - // Include custom rule paths into classpath - const customPathEntries = await this.getCustomRulePathEntries(); - customPathEntries.forEach((pathEntries) => { - classpathEntries.push(...pathEntries); - }); - - return classpathEntries; + protected constructor(opts: PmdSupportOptions) { + super({}); + this.rulePathsByLanguage = opts.rulePathsByLanguage; } - protected async getCustomRulePathEntries(): Promise>> { - const customRulePathManager = await Controller.createRulePathManager(); - return customRulePathManager.getRulePathEntries(PmdEngine.ENGINE_NAME); + protected buildSharedClasspath(): string[] { + const classpath: string[] = []; + // Every entry in the rule paths map should be added to the classpath. + for (const rulePaths of this.rulePathsByLanguage.values()) { + classpath.push(...rulePaths); + } + return classpath; } } diff --git a/src/lib/pmd/PmdWrapper.ts b/src/lib/pmd/PmdWrapper.ts index a37812936..4b3525369 100644 --- a/src/lib/pmd/PmdWrapper.ts +++ b/src/lib/pmd/PmdWrapper.ts @@ -1,5 +1,6 @@ import {Logger} from '@salesforce/core'; -import {Format, PmdSupport} from './PmdSupport'; +import {PMD_LIB} from '../../Constants'; +import {PmdSupport, PmdSupportOptions} from './PmdSupport'; import * as JreSetupManager from './../JreSetupManager'; import path = require('path'); import {FileHandler} from '../util/FileHandler'; @@ -7,23 +8,23 @@ import {FileHandler} from '../util/FileHandler'; const MAIN_CLASS = 'net.sourceforge.pmd.PMD'; const HEAP_SIZE = '-Xmx1024m'; -interface PmdWrapperOptions { - path: string; +type PmdWrapperOptions = PmdSupportOptions & { + targets: string[]; rules: string; - reportFormat?: Format; - reportFile?: string; -} +}; export default class PmdWrapper extends PmdSupport { - - - path: string; - rules: string; - reportFormat: Format; - reportFile: string; - logger: Logger; // TODO: Add relevant trace log lines + private targets: string[]; + private rules: string; + private logger: Logger; private initialized: boolean; + public constructor(opts: PmdWrapperOptions) { + super(opts); + this.targets = opts.targets; + this.rules = opts.rules; + } + protected async init(): Promise { if (this.initialized) { return; @@ -33,57 +34,30 @@ export default class PmdWrapper extends PmdSupport { this.initialized = true; } - protected async buildClasspath(): Promise { - return super.buildSharedClasspath(); - } - - public static async execute(path: string, rules: string, reportFormat?: Format, reportFile?: string): Promise { - const myPmd = await PmdWrapper.create({ - path: path, - rules: rules, - reportFormat: reportFormat, - reportFile: reportFile - }); - return myPmd.execute(); - } - - private async execute(): Promise { + public async execute(): Promise { return super.runCommand(); } - constructor(options: PmdWrapperOptions) { - super(options); - this.path = options.path; - this.rules = options.rules; - this.reportFormat = options.reportFormat || Format.XML; - this.reportFile = options.reportFile || null; - } - protected async buildCommandArray(): Promise<[string, string[]]> { const javaHome = await JreSetupManager.verifyJreSetup(); const command = path.join(javaHome, 'bin', 'java'); - // Start with the arguments we know we'll always need. - // NOTE: If we were going to run this command from the CLI directly, then we'd wrap the classpath in quotes, but this - // is intended for child_process.spawn(), which freaks out if you do that. - const classpath = await this.buildClasspath(); + // 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 = [`${PMD_LIB}/*`, ...this.buildSharedClasspath()].join(path.delimiter); // Operating systems impose limits on the maximum length of a command line invocation. This can be problematic - // when scannning a large number of files. Store the list of files to scan in a temp file. Pass the location + // when scanning a large number of files. Store the list of files to scan in a temp file. Pass the location // of the temp file to PMD. The temp file is cleaned up when the process exits. const fileHandler = new FileHandler(); const tmpPath = await fileHandler.tmpFileWithCleanup(); - await fileHandler.writeFile(tmpPath, this.path); - const args = ['-cp', classpath.join(path.delimiter), HEAP_SIZE, MAIN_CLASS, '-filelist', tmpPath, - '-format', this.reportFormat]; + await fileHandler.writeFile(tmpPath, this.targets.join(',')); + const args = ['-cp', classpath, HEAP_SIZE, MAIN_CLASS, '-filelist', tmpPath, + '-format', 'xml']; if (this.rules.length > 0) { args.push('-rulesets', this.rules); } - // Then add anything else that's dynamically included based on other input. - if (this.reportFile) { - args.push('-reportfile', this.reportFile); - } - this.logger.trace(`Preparing to execute PMD with command: "${command}", args: "${JSON.stringify(args)}"`); return [command, args]; } diff --git a/src/lib/services/CommandLineSupport.ts b/src/lib/services/CommandLineSupport.ts index 03afc1628..f1b8485f7 100644 --- a/src/lib/services/CommandLineSupport.ts +++ b/src/lib/services/CommandLineSupport.ts @@ -41,8 +41,6 @@ export abstract class CommandLineSupport extends AsyncCreatable { this.parentInitialized = true; } - protected abstract buildClasspath(): Promise; - /** * Returns a {@link SpinnerManager} implementation to be used while waiting for the child process to complete. This * default implementation returns a {@link NoOpSpinnerManager}, but subclasses may override to return another object @@ -85,7 +83,6 @@ export abstract class CommandLineSupport extends AsyncCreatable { // hold onto data only if it was not processed stdout += data; } - })(); }); cp.stderr.on('data', data => { @@ -95,7 +92,6 @@ export abstract class CommandLineSupport extends AsyncCreatable { // hold onto data only if it was not processed stderr += data; } - })(); }); diff --git a/src/lib/sfge/SfgeWrapper.ts b/src/lib/sfge/SfgeWrapper.ts index 12c8332ff..b9bb64871 100644 --- a/src/lib/sfge/SfgeWrapper.ts +++ b/src/lib/sfge/SfgeWrapper.ts @@ -121,10 +121,6 @@ abstract class AbstractSfgeWrapper extends CommandLineSupport { this.initialized = true; } - protected buildClasspath(): Promise { - return Promise.resolve([`${SFGE_LIB}/*`]); - } - protected isSuccessfulExitCode(code: number): boolean { return code === EXIT_NO_VIOLATIONS || code === EXIT_WITH_VIOLATIONS; } @@ -157,7 +153,7 @@ abstract class AbstractSfgeWrapper extends CommandLineSupport { protected async buildCommandArray(): Promise<[string, string[]]> { const javaHome = await JreSetupManager.verifyJreSetup(); const command = path.join(javaHome, 'bin', 'java'); - const classpath = await this.buildClasspath(); + const classpath = [`${SFGE_LIB}/*`]; const args = [`-Dsfge_log_name=${this.logFilePath}`, '-cp', classpath.join(path.delimiter)]; if (this.jvmArgs != null) { diff --git a/test/lib/pmd/PmdCatalogWrapper.test.ts b/test/lib/pmd/PmdCatalogWrapper.test.ts index f584b4154..ecd2d92d2 100644 --- a/test/lib/pmd/PmdCatalogWrapper.test.ts +++ b/test/lib/pmd/PmdCatalogWrapper.test.ts @@ -1,13 +1,9 @@ import {PmdCatalogWrapper} from '../../../src/lib/pmd/PmdCatalogWrapper'; -import {PmdEngine} from '../../../src/lib/pmd/PmdEngine'; -import {CustomRulePathManager} from '../../../src/lib/CustomRulePathManager'; import {Config} from '../../../src/lib/util/Config'; import {expect} from 'chai'; import Sinon = require('sinon'); import path = require('path'); -import {ENGINE,LANGUAGE,PMD_VERSION} from '../../../src/Constants'; -import {FileHandler} from '../../../src/lib/util/FileHandler'; -import {uxEvents, EVENTS} from '../../../src/lib/ScannerEvents'; +import {ENGINE,LANGUAGE} from '../../../src/Constants'; import { after } from 'mocha'; // In order to get access to PmdCatalogWrapper's protected methods, we're going to extend it with a test class here. @@ -15,12 +11,7 @@ class TestablePmdCatalogWrapper extends PmdCatalogWrapper { public async buildCommandArray(): Promise<[string, string[]]> { return super.buildCommandArray(); } - - public async getRulePathEntries(): Promise>> { - return super.getRulePathEntries(); - } } -const irrelevantPath = path.join('this', 'path', 'does', 'not', 'actually', 'matter'); describe('PmdCatalogWrapper', () => { describe('buildCommandArray()', () => { @@ -45,7 +36,9 @@ describe('PmdCatalogWrapper', () => { thePath, 'sfdc.sfdx.scanner.pmd.Main']; - const target = await TestablePmdCatalogWrapper.create({}); + const target = await TestablePmdCatalogWrapper.create({ + rulePathsByLanguage: new Map>() + }); const params = (await target.buildCommandArray())[1]; expectedParamList.forEach((value: string, index: number) => { @@ -53,146 +46,7 @@ describe('PmdCatalogWrapper', () => { }); }); }); - describe('When Custom PMD JARs have been registered for a language whose default PMD rules are off...', () => { - before(() => { - Sinon.createSandbox(); - // Spoof a config that claims that only Apex's default PMD JAR is enabled. - Sinon.stub(Config.prototype, 'getSupportedLanguages').withArgs(ENGINE.PMD).resolves([LANGUAGE.APEX]); - // Spoof a CustomPathManager that claims that a custom JAR exists for Java. - const customJars: Map> = new Map(); - customJars.set(LANGUAGE.JAVA, new Set([irrelevantPath])); - Sinon.stub(CustomRulePathManager.prototype, 'getRulePathEntries').withArgs(PmdEngine.ENGINE_NAME).resolves(customJars); - Sinon.stub(FileHandler.prototype, 'exists').resolves(true); - }); - - after(() => { - Sinon.restore(); - }); - - it('Custom PMD JARs are included', async () => { - // Get our parameters. - const target = await TestablePmdCatalogWrapper.create({}); - const params = (await target.buildCommandArray())[1]; - - const customJarValue = `java=${irrelevantPath}`; - const defaultJarPattern = /^apex=.*pmd-apex-.*.jar$/; - - validateCustomAndDefaultJarParams(params, customJarValue, defaultJarPattern); - }); - }); - - describe('When Custom PMD JARs have been registered for a language under a weird alias...', () => { - before(() => { - Sinon.createSandbox(); - // Spoof a config that claims that only Apex's default PMD JAR is enabled. - Sinon.stub(Config.prototype, 'getSupportedLanguages').withArgs(ENGINE.PMD).resolves([LANGUAGE.APEX]); - // Spoof a CustomPathManager that claims that a custom JAR exists for plsql, using a weird alias for that language. - const customJars: Map> = new Map(); - customJars.set('ViSuAlFoRcE', new Set([irrelevantPath])); - Sinon.stub(CustomRulePathManager.prototype, 'getRulePathEntries').withArgs(PmdEngine.ENGINE_NAME).resolves(customJars); - Sinon.stub(FileHandler.prototype, 'exists').resolves(true); - }); - - after(() => { - Sinon.restore(); - }); - - it('Custom PMD JARs are included', async () => { - // Get our parameters. - const target = await TestablePmdCatalogWrapper.create({}); - const params = (await target.buildCommandArray())[1]; - - const customJarValue = `visualforce=${irrelevantPath}`; - const defaultJarPattern = /^apex=.*pmd-apex-.*.jar$/; - validateCustomAndDefaultJarParams(params, customJarValue, defaultJarPattern); - }); - }); - - describe("When not all supported languages have an associated PMD JAR", () => { - before(() => { - Sinon.createSandbox(); - // Spoof a config that claims that only apex is the supported language - Sinon.stub(Config.prototype, 'getSupportedLanguages').withArgs(ENGINE.PMD).resolves([LANGUAGE.APEX]); - const customJars: Map> = new Map(); - customJars.set('visualforce', new Set([irrelevantPath])); - customJars.set(LANGUAGE.JAVA, new Set()); - Sinon.stub(CustomRulePathManager.prototype, 'getRulePathEntries').withArgs(PmdEngine.ENGINE_NAME).resolves(customJars); - Sinon.stub(FileHandler.prototype, 'exists').resolves(true); - }); - - after(() => { - Sinon.restore(); - }); - - it('should not include a supported language as input to PmdCataloger if the language has no associated path', async () => { - // Get our parameters. - const target = await TestablePmdCatalogWrapper.create({}); - const params = (await target.buildCommandArray())[1]; - - const customJarValue = `visualforce=${irrelevantPath}`; - const defaultJarPattern = /^apex=.*jar$/; - - validateCustomAndDefaultJarParams(params, customJarValue, defaultJarPattern); - }); - }); - - describe('Missing Rule Files are Handled Gracefully', () => { - 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-${PMD_VERSION}.jar`)); - let uxSpy = null; - - before(() => { - Sinon.createSandbox(); - Sinon.stub(Config.prototype, 'getSupportedLanguages').withArgs(ENGINE.PMD).resolves([LANGUAGE.JAVA]); - const customJars: Map> = new Map(); - // Simulate CustomPaths.json contains a jar that has been deleted or moved - customJars.set(LANGUAGE.JAVA, new Set([validJar, missingJar])); - Sinon.stub(CustomRulePathManager.prototype, 'getRulePathEntries').withArgs(PmdEngine.ENGINE_NAME).resolves(customJars); - const stub = Sinon.stub(FileHandler.prototype, 'exists'); - stub.withArgs(validJar).resolves(true); - stub.withArgs(missingJar).resolves(false); - uxSpy = Sinon.spy(uxEvents, 'emit'); - }); - - after(() => { - Sinon.restore(); - }); - - it('Missing file should be filtered out and display warning', async () => { - const target = await TestablePmdCatalogWrapper.create({}); - const entries = await target.getRulePathEntries(); - - // The rule path entries should only include the jar that exists - expect(entries.size).to.equal(1, `Entries: ${Array.from(entries.keys())}`); - const jars = entries.get(LANGUAGE.JAVA); - const jarsErrorMessage = `Jars: ${Array.from(jars)}`; - expect(jars.size).to.equal(2, jarsErrorMessage); - expect(jars).to.contain(validJar, jarsErrorMessage); - expect(jars).to.contain(pmdJar, jarsErrorMessage); - expect(jars).to.not.contain(missingJar, jarsErrorMessage); - - // A warning should be displayed - Sinon.assert.calledOnce(uxSpy); - Sinon.assert.calledWith(uxSpy, EVENTS.WARNING_ALWAYS, `Custom rule file path [${missingJar}] for language [${LANGUAGE.JAVA}] was not found.`); - }); - }); }); }); }); -function validateCustomAndDefaultJarParams(params: string[], customJarValue: string, defaultJarPattern: RegExp) { - const expectedParamLength = 7; - expect(params.length).to.equal(expectedParamLength, `Should have been ${expectedParamLength} parameters: ${params}`); - - const classpathIndex = expectedParamLength - 4; - expect(params[classpathIndex]).to.contain(irrelevantPath, `Parameter as position ${classpathIndex} should be classpath, including custom Java JAR`); - - const customJarIndex = expectedParamLength - 2; - expect(params[customJarIndex]).to.equal(customJarValue, `Parameter as position ${customJarIndex} is incorrect`); - - const defaultJarIndex = expectedParamLength - 1; - expect(params[defaultJarIndex]).to.match(defaultJarPattern, `Parameter as position ${defaultJarIndex} does not match pattern`); -} - diff --git a/test/lib/pmd/PmdEngine.test.ts b/test/lib/pmd/PmdEngine.test.ts index b5ebb7b20..c7a0398fb 100644 --- a/test/lib/pmd/PmdEngine.test.ts +++ b/test/lib/pmd/PmdEngine.test.ts @@ -4,12 +4,15 @@ import {RuleResult, RuleViolation} from '../../../src/types'; import path = require('path'); import {expect} from 'chai'; import Sinon = require('sinon'); -import {PmdEngine} from '../../../src/lib/pmd/PmdEngine' +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 } from '../../../src/Constants'; +import {CUSTOM_CONFIG, ENGINE, LANGUAGE, PMD_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"; TestOverrides.initializeTestSetup(); @@ -324,3 +327,127 @@ describe('Tests for BasePmdEngine and PmdEngine implementation', () => { }); }); }); + +describe('_PmdRuleMapper', () => { + const irrelevantPath = path.join('this', 'path', 'does', 'not', 'actually', 'matter'); + describe('When Custom PMD JARs have been registered for a language whose default PMD rules are off...', () => { + before(() => { + Sinon.createSandbox(); + // Spoof a config that claims that only Apex's default PMD JAR is enabled. + Sinon.stub(Config.prototype, 'getSupportedLanguages').withArgs(ENGINE.PMD).resolves([LANGUAGE.APEX]); + // Spoof a CustomPathManager that claims that a custom JAR exists for Java. + const customJars: Map> = new Map>(); + customJars.set(LANGUAGE.JAVA, new Set([irrelevantPath])); + Sinon.stub(CustomRulePathManager.prototype, 'getRulePathEntries').withArgs(PmdEngine.ENGINE_NAME).returns(customJars); + Sinon.stub(FileHandler.prototype, 'exists').resolves(true); + }); + + after(() => { + Sinon.restore(); + }); + + it('Custom PMD JARs are included', async () => { + // Get our parameters. + const target = await _PmdRuleMapper.create({}); + const ruleMap = await target.createStandardRuleMap(); + + expect(ruleMap).to.include.keys(LANGUAGE.JAVA); + expect(ruleMap.get(LANGUAGE.JAVA)).to.have.key(irrelevantPath); + }); + }); + + describe('When Custom PMD JARs have been registered for a language under a weird alias...', () => { + before(() => { + Sinon.createSandbox(); + // Spoof a config that claims that only Apex's default PMD JAR is enabled. + Sinon.stub(Config.prototype, 'getSupportedLanguages').withArgs(ENGINE.PMD).resolves([LANGUAGE.APEX]); + // Spoof a CustomPathManager that claims that a custom JAR exists for plsql, using a weird alias for that language. + const customJars: Map> = new Map(); + customJars.set('ViSuAlFoRcE', new Set([irrelevantPath])); + Sinon.stub(CustomRulePathManager.prototype, 'getRulePathEntries').withArgs(PmdEngine.ENGINE_NAME).returns(customJars); + Sinon.stub(FileHandler.prototype, 'exists').resolves(true); + }); + + after(() => { + Sinon.restore(); + }); + + it('Custom PMD JARs are included', async () => { + // Get our parameters. + const target = await _PmdRuleMapper.create({}); + const ruleMap = await target.createStandardRuleMap(); + + expect(ruleMap).to.include.keys(LANGUAGE.VISUALFORCE); + expect(ruleMap.get(LANGUAGE.VISUALFORCE)).to.have.key(irrelevantPath); + }); + }); + + describe("When not all supported languages have an associated PMD JAR", () => { + before(() => { + Sinon.createSandbox(); + // Spoof a config that claims that only apex is the supported language + Sinon.stub(Config.prototype, 'getSupportedLanguages').withArgs(ENGINE.PMD).resolves([LANGUAGE.APEX]); + const customJars: Map> = new Map(); + customJars.set('visualforce', new Set([irrelevantPath])); + customJars.set(LANGUAGE.JAVA, new Set()); + Sinon.stub(CustomRulePathManager.prototype, 'getRulePathEntries').withArgs(PmdEngine.ENGINE_NAME).returns(customJars); + Sinon.stub(FileHandler.prototype, 'exists').resolves(true); + }); + + after(() => { + Sinon.restore(); + }); + + it('should not include a supported language as input to PmdCataloger if the language has no associated path', async () => { + // Get our parameters. + const target = await _PmdRuleMapper.create({}); + const ruleMap = await target.createStandardRuleMap(); + + // Since Java had no JAR, it should not be included in map + expect(ruleMap).to.not.have.key(LANGUAGE.JAVA); + }); + }); + + describe('Missing Rule Files are Handled Gracefully', () => { + 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-${PMD_VERSION}.jar`)); + let uxSpy = null; + + before(() => { + Sinon.createSandbox(); + Sinon.stub(Config.prototype, 'getSupportedLanguages').withArgs(ENGINE.PMD).resolves([LANGUAGE.JAVA]); + const customJars: Map> = new Map(); + // Simulate CustomPaths.json contains a jar that has been deleted or moved + customJars.set(LANGUAGE.JAVA, new Set([validJar, missingJar])); + Sinon.stub(CustomRulePathManager.prototype, 'getRulePathEntries').withArgs(PmdEngine.ENGINE_NAME).returns(customJars); + const stub = Sinon.stub(FileHandler.prototype, 'exists'); + stub.withArgs(validJar).resolves(true); + stub.withArgs(missingJar).resolves(false); + uxSpy = Sinon.spy(uxEvents, 'emit'); + }); + + after(() => { + Sinon.restore(); + }); + + it('Missing file should be filtered out and display warning', async () => { + const target = await _PmdRuleMapper.create({}); + const ruleMap = await target.createStandardRuleMap(); + + // The rule path entries should only include the jar that exists + expect(ruleMap.size).to.equal(1); + const jars = ruleMap.get(LANGUAGE.JAVA); + const jarsErrorMessage = `Jars: ${Array.from(jars)}`; + expect(jars.size).to.equal(2, jarsErrorMessage); + expect(jars).to.contain(validJar, jarsErrorMessage); + expect(jars).to.contain(pmdJar, jarsErrorMessage); + expect(jars).to.not.contain(missingJar, jarsErrorMessage); + + // A warning should be displayed + Sinon.assert.calledOnce(uxSpy); + Sinon.assert.calledWith(uxSpy, EVENTS.WARNING_ALWAYS, `Custom rule file path [${missingJar}] for language [${LANGUAGE.JAVA}] was not found.`); + }); + }); +}); From 4a4cde2db6f19aa26e8999cd735302758f2c2cea Mon Sep 17 00:00:00 2001 From: Josh Feingold Date: Mon, 18 Dec 2023 17:00:08 -0600 Subject: [PATCH 03/10] @W-13222948@: Added AppExchange PMD subvariant. --- pmd-appexchange/lib/pmd-xml-sf-0.0.1.jar | Bin 0 -> 2931 bytes pmd-appexchange/lib/sfca-pmd-apex-0.12.jar | Bin 0 -> 9140 bytes pmd-appexchange/lib/sfca-pmd-html-0.12.jar | Bin 0 -> 1946 bytes .../lib/sfca-pmd-javascript-0.12.jar | Bin 0 -> 1873 bytes .../lib/sfca-pmd-visualforce-0.12.jar | Bin 0 -> 7568 bytes pmd-appexchange/lib/sfca-pmd-xml-0.12.jar | Bin 0 -> 4153 bytes .../java/sfdc/sfdx/scanner/Constants.java | 10 +++ .../main/java/sfdc/sfdx/scanner/pmd/Main.java | 6 +- .../sfdx/scanner/pmd/PmdRuleCataloger.java | 40 ++++++--- .../scanner/pmd/catalog/PmdCatalogJson.java | 8 +- .../scanner/pmd/catalog/PmdCatalogRule.java | 11 ++- .../pmd/Pmd7CompatibilityCheckerTest.java | 5 +- .../scanner/pmd/PmdRuleCatalogerTest.java | 13 +-- .../pmd/catalog/PmdCatalogJsonTest.java | 7 +- .../pmd/catalog/PmdCatalogRuleTest.java | 9 +- src/Constants.ts | 7 ++ src/ioc.config.ts | 3 +- src/lib/pmd/PmdCatalogWrapper.ts | 10 ++- src/lib/pmd/PmdEngine.ts | 80 ++++++++++++++++-- src/lib/pmd/PmdWrapper.ts | 11 ++- src/lib/util/Config.ts | 35 ++++++++ test/commands/scanner/rule/list.test.ts | 2 +- test/lib/Controller.test.ts | 8 +- test/lib/pmd/PmdCatalogWrapper.test.ts | 4 +- 24 files changed, 208 insertions(+), 61 deletions(-) create mode 100644 pmd-appexchange/lib/pmd-xml-sf-0.0.1.jar create mode 100644 pmd-appexchange/lib/sfca-pmd-apex-0.12.jar create mode 100644 pmd-appexchange/lib/sfca-pmd-html-0.12.jar create mode 100644 pmd-appexchange/lib/sfca-pmd-javascript-0.12.jar create mode 100644 pmd-appexchange/lib/sfca-pmd-visualforce-0.12.jar create mode 100644 pmd-appexchange/lib/sfca-pmd-xml-0.12.jar create mode 100644 pmd-cataloger/src/main/java/sfdc/sfdx/scanner/Constants.java diff --git a/pmd-appexchange/lib/pmd-xml-sf-0.0.1.jar b/pmd-appexchange/lib/pmd-xml-sf-0.0.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..b5c63ae6d81021aefdcc1064d8da14e0401f3692 GIT binary patch literal 2931 zcma)83pmqzA7_|LGHH~v*5zhrblwIs=(T00o-BfUhjXVeAju+Bi9* zakg`~_9ut1`sl8;Sbg2jc7Dx~pL=$SMu$fs5@G;t>Yp_cVUgkf zgut+HB4O2faDkxa%6f8Wz^WW@W8dS1;7<@&C2Frmj0g-RQ1AhG3jQ07bBEPPt#VRQ zn}O4?`I{O}wzxm#+(SbS0V|8d6A8GmfXEO6+CKyz5s{NYjE0-SnU!P&!oR&JbFdm` z*r*Aq_@$~DZ^~rI7UXnItK_z}mt(0jjlbDZ4YXqw$F#TY+j?gE;#O2;;4`7hZWPKl zS8F_#UFKFfJ-y}3zh^DhO|g#~@3IzbJN!8GqOUS8eeTh{;5A-l9T$C?^m%u5Gcr;L zo~3p7`z=SzPb+8;eC9v7b~!>_luCDZ4-p-Ve<-YQvS#MBCWpIc^c{ShKQ+3x)Y=_N z$IQo15Dd|>`TIgVb$69W=a+9m^z0x7nq0|cXzDPeHF}QO)#O0~HomN$ih*{lcP84k zjUVwTCmN{Mw!HJM6F-0Je)JAq%fI(#IL9rw>VTji5vIh;QS(lr92%OtqN!JAjjDGR z+(Hi_T=ClTrU|^7&u^~E)4e=0YrO(_~F+} zYLSWdEp2YkJqGuvXHpUFR%VTy@N$|WGTQ%x_7CUm8ZNuN)H{#*aU?oWK*Q9O`-z0x z8;aFpkZ~u2;Txt77RTIDfDUXU;qAHg8-rsG2wfOCO*1(V=HHvH9wzLE|;&uTb_FkeF*C2F_V} ztJ|1=ia*Oq#dvJuB9m_SFO-bbg(@0(6~BxwilW*JLSPL?z4LF813di#rOQr*3p-K6 zq%s6$onH(sWNf%v3SqIkz}KhCy0s{Z>lT!#21mVC{LNZtm(7%FfvgdlBD%4^y2q}K zDt^jhsHqoA-C2 zK1v%Nagd{(Pup_XuOY|Y{+;%MBZfI*GZ`d?pE5eH)Np-I^03B}atz-;?c}V7&sn7H zrAr@;nl!6L47ZD-^E`8oX@FqTo!A09XV(|IQ>W4u>3zNKxHyMOWeu*c{l|-i0Ua&l z+pZ%D^6%ES+%+5O|G-xo__Ao*IbhynL=woymoWFjYw<5+-+Fd2hb|W$d6rp~S|Mm# zaJVMQ?$A$&)E3uJ7qXk;8lK*QI$p@n%+spFo_>LARo`*gAgqtZk*h45-T&(uSa$d% zl?w4xQypv*h(g;*p9I zSNC!k-#aCjPuTI<&-x}SKC{B=#9Cdtq?4Na-tR0)Y+_dmKADfZU?uZ7R z4*k_sF7T_B;(k_^6%3?tiMu^iH&ND_OkLVl6x7A9OUP32PDKo^WtGGS>7nHGFK$hi zZ-u|oU|OFXZ^UaCHyPr_S)OAi?FMtoF=<^GSGo(~il=I+xcbMCnwZHCoiYwsX$V~A zf5#AD%>Y!TrZC|D@`fd8lA#1F2EnH50MBLGiijeq09Jp^KVTAB0@yeTn1Xp=H3pbV zmH;v`fvIGISd9&)lqG;{9x#<;gPCPD1u(NL0aO4_`+ExiO)#s;fC**^pp2>XDl#kb zjHIGup82ZDrpNqE#s8abBwKEBwzBLd{to6`@YKuzV7O` U#sMn_1x1vApAc9XLjipI4{MD%*8l(j literal 0 HcmV?d00001 diff --git a/pmd-appexchange/lib/sfca-pmd-apex-0.12.jar b/pmd-appexchange/lib/sfca-pmd-apex-0.12.jar new file mode 100644 index 0000000000000000000000000000000000000000..cdcecec2d8b46cda633376f8cc9d589ba33e2c78 GIT binary patch literal 9140 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 literal 0 HcmV?d00001 diff --git a/pmd-appexchange/lib/sfca-pmd-html-0.12.jar b/pmd-appexchange/lib/sfca-pmd-html-0.12.jar new file mode 100644 index 0000000000000000000000000000000000000000..a52419d76cae3d218a95bed515ca3a099792865c GIT binary patch literal 1946 zcmWIWW@h1HVBp|j$V-|O&Hw~VAOZ+Df!NnI#8KDN&rP41Apk|;rh2A#(m(~0KrDi+ z(AUw=)6F$FM9JV;-BacXjDQD#Y{UPW$B z@07d#w+#di%%8pg(Fx{T7lg_`Tx-qZFyrOi(%ID35|H|6@y2KUXUb-My8nL868Rp^ zYtya1*Y3C8UC`^)&&YGVV$QjJ^X47!jIEEHDr(N%A~~&h@NM9}pB=5gFKMy5MoR5FaAQH3X2zF&zxRCP zSf#6e=+)!D_j`Hgmps2Zf8o@HO_Quy@>s5%W7A>&z}heJu*}SRVaT1!8#*o+^G){hDzHe=)Y1JV|w6 z^SY$Lcx6sU;%wse6LbbpNfh#IrWWVK>N&3BtJ1eJw zU21;NHr2=5oW8$0qG@G1;kZJ5{zJBiw@Z3&TiI&ec(!Npanb8r4{5wgnIKYKzC2z1 zx_bHDbioNeIG!6TDcflEhC?;D+cXyvEX*Zkwk7Qabh z3z}KJf4_WjTj{pTzs07ny~x^G=e^`|PKf{Ex3P;pAF*mx4d<=B96Wi`_Bmmuch}}E zG*4@Oy5hy7FRKDK8M7YaduJzqN3-gCh}hvh)%A1uzFc~@?zwcw_wSzfAD{k^qy8_i zH7EAbvTrHwRt+96p1!(wDl=%!{WF=L*ikb$a#jPC1VyDesl}-!#TdB^Ss^G-!WCl8 zleVwj)!zVBonT~Oki}39%9Bw2;LJGTY+j3kh}-qK%NM-)x$>^0=}e!?QU5nufITkEyVw8UBOIKIz$|2XJJWyk`iO@~$pZC}4!Icw^iy?o(c{uv+6FkRd# z#INXMnLODtxM1`2RptK{tq5(*HPl%==acV=@C*AdZPF>JTo#xh`P~1_=9STw8p<8p z1X`wQMJ*`)v2V>>5l8Fa|NW*o+)&)QyD|3K$G*#==XO}xh@`V#daZdiWOudr8@bM7 z%8G(>6fK-z^#?1z(K$AKd)ovPU)>X{_Z|88_SK_5Q+W^e@KkB;DtVQlz2IZ@jXa^W zdbPVhJ9YAAhqmhncr!AIFyk(XLBY(xAi(g}5k$kwWpr)m8J!K<2k%*xeEu9B=vjVFqU?mKMe}EaIgB8RB0I(IfG5`Po literal 0 HcmV?d00001 diff --git a/pmd-appexchange/lib/sfca-pmd-javascript-0.12.jar b/pmd-appexchange/lib/sfca-pmd-javascript-0.12.jar new file mode 100644 index 0000000000000000000000000000000000000000..298093c61ee6a6599c00a8c9abcb67135711c0a7 GIT binary patch literal 1873 zcmWIWW@h1HVBp|j$V-|O&Hw~VAOZ+Df!NnI#8KDN&rP41Apk|;rh2A#(m(~0KrDi+ z(AUw=)6F$FM97iAWdVCebd;8K(e^iwV?1A_q$J;iCsiSYw&%>OXQ%x_V)7eo8Zy>?sV?G==8W|! z+j{Ap)!IGzYCWc5OrulY1&27dU0+;UUQgmlQQ2}`y68d6T=vyFcU{f?oO1Qx z)r7FlkW(fJD<69__HiyaanMUyraMJytC4E>wA-~D+fCV$iX}3ZSqbJz*`E~cHRZj3 z{JNSQO>fpNe z!gqaFrJPBGxXJZPOuQ4WI~T86xA9WT!&z}p-|Vov&9D1WD5j&Y>gNWTZQuSnh)$e@Qp0ocfpd+uV&$9n2m)+Mp*q=kfka{Ko(G|2eG^ zskeOb_EYKVU*}t$VB_w;r>1^!Um&O4w;$_gs{Q8-GO7~RJLtPYHz>aBo^|;YR=~V z)AA?NiZ|_0JuYx;(&vj`cw)bBu|4NMpu@87+J~m6I`Pa;dw!Xl_zKUCi{$#CbUyya z_3F#3lqEd39#vS|G{q_Q(fJL9UTkmQJU*Ah_>phrT~U$N6Rzjd-gxexLo6->((s}XpEgjL2>~GY&IdB#+K^HTx(Vnd zD9nr{jhAtlfUi77Hyvaec5n4DBc)1w+EBf<9a$S5Z=q)ZgtuPfFaa$)1bDNufi!Ud Op%l;yiohC%fdK%`+>E#Y literal 0 HcmV?d00001 diff --git a/pmd-appexchange/lib/sfca-pmd-visualforce-0.12.jar b/pmd-appexchange/lib/sfca-pmd-visualforce-0.12.jar new file mode 100644 index 0000000000000000000000000000000000000000..8efa54a4fa9ac7610ba73cacf2b919118635d35c GIT binary patch literal 7568 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 Xtdbl8;sY4t{`Kts;Z%RnQeplFX6lAI literal 0 HcmV?d00001 diff --git a/pmd-appexchange/lib/sfca-pmd-xml-0.12.jar b/pmd-appexchange/lib/sfca-pmd-xml-0.12.jar new file mode 100644 index 0000000000000000000000000000000000000000..09860a70cce6ff2e1c843dc7140b25dea1c70bc6 GIT binary patch literal 4153 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 A3;+NC literal 0 HcmV?d00001 diff --git a/pmd-cataloger/src/main/java/sfdc/sfdx/scanner/Constants.java b/pmd-cataloger/src/main/java/sfdc/sfdx/scanner/Constants.java new file mode 100644 index 000000000..bcc7d8cef --- /dev/null +++ b/pmd-cataloger/src/main/java/sfdc/sfdx/scanner/Constants.java @@ -0,0 +1,10 @@ +package sfdc.sfdx.scanner; + +public class Constants { + + public static final class SystemProperty { + public static final String CATALOG_HOME = "catalogHome"; + public static final String CATALOG_NAME = "catalogName"; + public static final String CATALOGED_ENGINE_NAME = "catalogedEngineName"; + } +} diff --git a/pmd-cataloger/src/main/java/sfdc/sfdx/scanner/pmd/Main.java b/pmd-cataloger/src/main/java/sfdc/sfdx/scanner/pmd/Main.java index 55aa92c55..d00afc5e9 100644 --- a/pmd-cataloger/src/main/java/sfdc/sfdx/scanner/pmd/Main.java +++ b/pmd-cataloger/src/main/java/sfdc/sfdx/scanner/pmd/Main.java @@ -6,6 +6,7 @@ import com.salesforce.messaging.EventKey; import com.salesforce.messaging.MessagePassableException; import com.salesforce.messaging.CliMessager; +import sfdc.sfdx.scanner.Constants; public class Main { @@ -111,7 +112,10 @@ private void parseArg(Map> languagePathEntries, String arg) static class Dependencies { PmdRuleCataloger getPmdRuleCataloger(Map> rulePathEntries) { - return new PmdRuleCataloger(rulePathEntries); + String catalogHome = System.getProperty(Constants.SystemProperty.CATALOG_HOME); + String catalogName = System.getProperty(Constants.SystemProperty.CATALOG_NAME); + String catalogedEngineName = System.getProperty(Constants.SystemProperty.CATALOGED_ENGINE_NAME); + return new PmdRuleCataloger(rulePathEntries, catalogHome, catalogName, catalogedEngineName); } } } 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 2647faa53..8f5f84f3e 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 @@ -19,26 +19,42 @@ import sfdc.sfdx.scanner.paths.PathManipulator; class PmdRuleCataloger { - private Map> rulePathEntries; + private final Map> rulePathEntries; // Holds category and rulesets maps that provide files we need to scan for each language. - private LanguageXmlFileMapping languageXmlFileMapping = new LanguageXmlFileMapping(); + private final LanguageXmlFileMapping languageXmlFileMapping = new LanguageXmlFileMapping(); // These maps are going to help us store intermediate objects in an easy-to-reference way. - private Map> rulesByLanguage = new HashMap<>(); - private Map> rulesetsByLanguage = new HashMap<>(); + private final Map> rulesByLanguage = new HashMap<>(); + private final Map> rulesetsByLanguage = new HashMap<>(); // These lists are going to be the master lists that we ultimately use to build our JSON at the end. - private List masterCategoryList = new ArrayList<>(); - private List masterRuleList = new ArrayList<>(); - private List masterRulesetList = new ArrayList<>(); + private final List masterCategoryList = new ArrayList<>(); + 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; /** * @param rulePathEntries Map of languages and their file resources. Includes both inbuilt PMD and custom rules provided by user */ - PmdRuleCataloger(Map> rulePathEntries) { + PmdRuleCataloger(Map> rulePathEntries, String catalogHome, String catalogName, String engineSubvariant) { this.rulePathEntries = rulePathEntries; + this.catalogHome = catalogHome; + this.catalogName = catalogName; + this.engineSubvariant = engineSubvariant; } @@ -84,7 +100,7 @@ void catalogRules() { new Pmd7CompatibilityChecker().validatePmd7Readiness(masterRuleList); // STEP 6: Build a JSON using all of our objects. - PmdCatalogJson json = new PmdCatalogJson(masterRuleList, masterCategoryList, masterRulesetList); + PmdCatalogJson json = new PmdCatalogJson(masterRuleList, masterCategoryList, masterRulesetList, engineSubvariant); // STEP 7: Write the JSON to a file. writeJsonToFile(json); @@ -128,7 +144,7 @@ private void processCategoryFile(String language, String path) { int ruleCount = ruleNodes.getLength(); for (int i = 0; i < ruleCount; i++) { Element ruleNode = (Element) ruleNodes.item(i); - PmdCatalogRule rule = new PmdCatalogRule(ruleNode, category, language); + PmdCatalogRule rule = new PmdCatalogRule(ruleNode, category, language, engineSubvariant); rules.add(rule); } if (!this.rulesByLanguage.containsKey(language)) { @@ -180,9 +196,7 @@ private void linkRulesToRulesets(List rules, List rules; private final List categories; private final List rulesets; + private final String engineName; - public PmdCatalogJson(List rules, List categories, List rulesets) { + public PmdCatalogJson(List rules, List categories, List rulesets, String engineName) { this.rules = rules; this.categories = categories; this.rulesets = rulesets; + this.engineName = engineName; } /** @@ -89,7 +89,7 @@ private void addRulePath(Map pathsByAlias, String alias, Str JSONObject obj = pathsByAlias.get(alias); if (obj == null) { obj = new JSONObject(); - obj.put(JSON_ENGINE, PMD_ENGINE_NAME); + obj.put(JSON_ENGINE, this.engineName); obj.put(JSON_NAME, alias); obj.put(JSON_PATHS, new ArrayList()); pathsByAlias.put(alias, obj); diff --git a/pmd-cataloger/src/main/java/sfdc/sfdx/scanner/pmd/catalog/PmdCatalogRule.java b/pmd-cataloger/src/main/java/sfdc/sfdx/scanner/pmd/catalog/PmdCatalogRule.java index d2f0f6e61..4f2018370 100644 --- a/pmd-cataloger/src/main/java/sfdc/sfdx/scanner/pmd/catalog/PmdCatalogRule.java +++ b/pmd-cataloger/src/main/java/sfdc/sfdx/scanner/pmd/catalog/PmdCatalogRule.java @@ -54,8 +54,10 @@ public class PmdCatalogRule { private final Element element; + private final String engineName; - public PmdCatalogRule(Element element, PmdCatalogCategory category, String language) { + + public PmdCatalogRule(Element element, PmdCatalogCategory category, String language, String engineName) { this.element = element; this.name = element.getAttribute(ATTR_NAME); this.message = element.getAttribute(ATTR_MESSAGE); @@ -63,12 +65,9 @@ public PmdCatalogRule(Element element, PmdCatalogCategory category, String langu this.category = category; this.description = getDescription(element); this.sourceJar = category.getSourceJar(); + this.engineName = engineName; } - public String getLanguage() { - return language; - } - String getFullName() { return getCategoryPath() + "/" + getName(); } @@ -153,7 +152,7 @@ private String getDescription(Element element) { */ JSONObject toJson() { Map m = new HashMap<>(); - m.put(JSON_ENGINE, PMD_ENGINE_NAME); + m.put(JSON_ENGINE, this.engineName); m.put(JSON_NAME, this.name); m.put(JSON_MESSAGE, this.message); m.put(JSON_DESCRIPTION, this.description); 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 index 24a101597..8e5a5dc0e 100644 --- a/pmd-cataloger/src/test/java/sfdc/sfdx/scanner/pmd/Pmd7CompatibilityCheckerTest.java +++ b/pmd-cataloger/src/test/java/sfdc/sfdx/scanner/pmd/Pmd7CompatibilityCheckerTest.java @@ -31,6 +31,7 @@ public class Pmd7CompatibilityCheckerTest { 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. @@ -135,7 +136,7 @@ private PmdCatalogRule createJavaBasedRule(PmdCatalogCategory category, boolean 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"); + return new PmdCatalogRule(ruleElement, category, "apex", MOCKED_ENGINE_NAME); } private PmdCatalogRule createXpathRule(PmdCatalogCategory category, String classProp, boolean hasLangProp) { @@ -153,7 +154,7 @@ private PmdCatalogRule createXpathRule(PmdCatalogCategory category, String class + ""; String ruleXml = createRuleXml(classProp, hasLangProp, propertiesTags); Element ruleElement = createRuleElement(ruleXml); - return new PmdCatalogRule(ruleElement, category, "apex"); + return new PmdCatalogRule(ruleElement, category, "apex", MOCKED_ENGINE_NAME); } private String createRuleXml(String classProp, boolean hasLangProp, String innerXml) { diff --git a/pmd-cataloger/src/test/java/sfdc/sfdx/scanner/pmd/PmdRuleCatalogerTest.java b/pmd-cataloger/src/test/java/sfdc/sfdx/scanner/pmd/PmdRuleCatalogerTest.java index 7b7214fdd..19a9af1fb 100644 --- a/pmd-cataloger/src/test/java/sfdc/sfdx/scanner/pmd/PmdRuleCatalogerTest.java +++ b/pmd-cataloger/src/test/java/sfdc/sfdx/scanner/pmd/PmdRuleCatalogerTest.java @@ -34,6 +34,7 @@ public class PmdRuleCatalogerTest { private static final String TEST_CATALOG_DIR = "./test/path/to/a/directory"; private static final String TEST_CATALOG_FILE = "PmdCatalog.json"; + private static final String TEST_ENGINE_NAME = "MockedEngineName"; public ArgumentCaptor jsonContentsCaptor; public ArgumentCaptor directoryPathCaptor; @@ -41,19 +42,11 @@ public class PmdRuleCatalogerTest { @BeforeEach public void setup() { - System.setProperty("catalogHome", TEST_CATALOG_DIR); - System.setProperty("catalogName", TEST_CATALOG_FILE); CliMessager.getInstance().resetMessages(); } - @AfterEach - public void teardown() { - System.clearProperty("catalogHome"); - System.clearProperty("catalogName"); - } - public PmdRuleCataloger createPmdRuleCatalogerSpy(Map> rulePathEntries) { - PmdRuleCataloger pmdRuleCataloger = new PmdRuleCataloger(rulePathEntries); + PmdRuleCataloger pmdRuleCataloger = new PmdRuleCataloger(rulePathEntries, TEST_CATALOG_DIR, TEST_CATALOG_FILE, TEST_ENGINE_NAME); PmdRuleCataloger pmdRuleCatalogerSpy = Mockito.spy(pmdRuleCataloger); jsonContentsCaptor = ArgumentCaptor.forClass(String.class); @@ -144,7 +137,7 @@ public void testExceptionIsThrownWhenCollisionOccurs() { rulePathEntries.put(APEX, Arrays.asList(COLLISION_JAR_1.toAbsolutePath().toString(), COLLISION_JAR_2.toAbsolutePath().toString())); - PmdRuleCataloger pmdRuleCataloger = new PmdRuleCataloger(rulePathEntries); + PmdRuleCataloger pmdRuleCataloger = new PmdRuleCataloger(rulePathEntries, TEST_CATALOG_DIR, TEST_CATALOG_FILE, TEST_ENGINE_NAME); MessagePassableException ex = assertThrows(MessagePassableException.class, () -> pmdRuleCataloger.catalogRules()); diff --git a/pmd-cataloger/src/test/java/sfdc/sfdx/scanner/pmd/catalog/PmdCatalogJsonTest.java b/pmd-cataloger/src/test/java/sfdc/sfdx/scanner/pmd/catalog/PmdCatalogJsonTest.java index 3e7fdfb69..01087a688 100644 --- a/pmd-cataloger/src/test/java/sfdc/sfdx/scanner/pmd/catalog/PmdCatalogJsonTest.java +++ b/pmd-cataloger/src/test/java/sfdc/sfdx/scanner/pmd/catalog/PmdCatalogJsonTest.java @@ -16,6 +16,7 @@ public class PmdCatalogJsonTest { private static final String RULE_PATH = "rule path"; private static final String CATEGORY_NAME = "category name"; private static final String CATEGORY_PATH = "category path"; + private static final String MOCKED_ENGINE_NAME = "MockedEngineName"; @Test public void testConstructJson() { @@ -27,7 +28,7 @@ public void testConstructJson() { categories.add(getPmdCatalogCategoryMock(CATEGORY_NAME, CATEGORY_PATH)); rulesets.add(getPmdCatalogRulesetMock(RULE_NAME, RULE_PATH)); - final PmdCatalogJson catalogJson = new PmdCatalogJson(rules, categories, rulesets); + final PmdCatalogJson catalogJson = new PmdCatalogJson(rules, categories, rulesets, MOCKED_ENGINE_NAME); // Execute final JSONObject jsonObject = catalogJson.constructJson(); @@ -35,11 +36,11 @@ public void testConstructJson() { // Verify //[{"paths":["rule path"],"name":"rule name"}] final String expectedRulesetJson = String.format("[{\"engine\":\"%s\",\"paths\":[\"%s\"],\"name\":\"%s\"}]", - PmdCatalogJson.PMD_ENGINE_NAME, RULE_PATH, RULE_NAME); + MOCKED_ENGINE_NAME, RULE_PATH, RULE_NAME); assertEquals(expectedRulesetJson, jsonObject.get(PmdCatalogJson.JSON_RULESETS).toString()); final String expectedCategoryJson = String.format("[{\"engine\":\"%s\",\"paths\":[\"%s\"],\"name\":\"%s\"}]", - PmdCatalogJson.PMD_ENGINE_NAME, CATEGORY_PATH, CATEGORY_NAME); + MOCKED_ENGINE_NAME, CATEGORY_PATH, CATEGORY_NAME); assertEquals(expectedCategoryJson, jsonObject.get(PmdCatalogJson.JSON_CATEGORIES).toString()); // Rules json has its own test where we verify the Json contents. Here, we only confirm that it exists diff --git a/pmd-cataloger/src/test/java/sfdc/sfdx/scanner/pmd/catalog/PmdCatalogRuleTest.java b/pmd-cataloger/src/test/java/sfdc/sfdx/scanner/pmd/catalog/PmdCatalogRuleTest.java index bf800e5cb..586939079 100644 --- a/pmd-cataloger/src/test/java/sfdc/sfdx/scanner/pmd/catalog/PmdCatalogRuleTest.java +++ b/pmd-cataloger/src/test/java/sfdc/sfdx/scanner/pmd/catalog/PmdCatalogRuleTest.java @@ -29,13 +29,14 @@ public class PmdCatalogRuleTest { private static final String CATEGORY_NAME = "Best Practices"; private static final String CATEGORY_PATH = "/some/path"; private static final String CATEGORY_SOURCEJAR = "/path/to/sourcejar.jar"; + private static final String MOCKED_ENGINE_NAME = "MockedEngineName"; private static final PmdCatalogCategory CATEGORY = new PmdCatalogCategory(CATEGORY_NAME, CATEGORY_PATH, CATEGORY_SOURCEJAR); @Test public void testCatalogRuleJsonConversion() { // Setup mock final Element elementMock = getElementMock(Collections.singletonList("description")); - final PmdCatalogRule catalogRule = new PmdCatalogRule(elementMock, CATEGORY, LANGUAGE); + final PmdCatalogRule catalogRule = new PmdCatalogRule(elementMock, CATEGORY, LANGUAGE, MOCKED_ENGINE_NAME); // Execute @@ -64,7 +65,7 @@ public void testCatalogRuleNoDescription() { // Setup mock final Element elementMock = getElementMock(Collections.singletonList("")); - final PmdCatalogRule catalogRule = new PmdCatalogRule(elementMock, CATEGORY, LANGUAGE); + final PmdCatalogRule catalogRule = new PmdCatalogRule(elementMock, CATEGORY, LANGUAGE, MOCKED_ENGINE_NAME); // Execute final JSONObject jsonObject = catalogRule.toJson(); @@ -79,7 +80,7 @@ public void testCatalogRuleJsonWithDescription() { // Setup mock final Element elementMock = getElementMock(Collections.singletonList(description)); - final PmdCatalogRule catalogRule = new PmdCatalogRule(elementMock, CATEGORY, LANGUAGE); + final PmdCatalogRule catalogRule = new PmdCatalogRule(elementMock, CATEGORY, LANGUAGE, MOCKED_ENGINE_NAME); // Execute final JSONObject jsonObject = catalogRule.toJson(); @@ -97,7 +98,7 @@ public void testCatalogRuleJsonWithMultipleDescriptions_expectException() { final Element elementMock = getElementMock(Arrays.asList(description1, description2)); // Even initializing the object should be enough to trigger the expected exception. - MessagePassableException ex = assertThrows(MessagePassableException.class, () -> new PmdCatalogRule(elementMock, CATEGORY, LANGUAGE)); + MessagePassableException ex = assertThrows(MessagePassableException.class, () -> new PmdCatalogRule(elementMock, CATEGORY, LANGUAGE, MOCKED_ENGINE_NAME)); assertThat(ex.getEventKey(), is(EventKey.ERROR_EXTERNAL_MULTIPLE_RULE_DESC)); assertThat(ex.getArgs(), is(new String[]{CATEGORY.getPath() + "/" + NAME, "2"})); } diff --git a/src/Constants.ts b/src/Constants.ts index 2a6957232..81b23f6ff 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -2,6 +2,7 @@ import os = require('os'); import path = require('path'); export const PMD_VERSION = '6.55.0'; +export const PMD_APPEXCHANGE_VERSION = '0.12'; export const SFGE_VERSION = '1.0.1-pilot'; export const DEFAULT_SCANNER_PATH = path.join(os.homedir(), '.sfdx-scanner'); export const CATALOG_FILE = 'Catalog.json'; @@ -20,6 +21,7 @@ export const INTERNAL_ERROR_CODE = 1; export enum ENGINE { PMD = 'pmd', + PMD_APPEXCHANGE = 'pmd-appexchange', PMD_CUSTOM = 'pmd-custom', ESLINT = 'eslint', ESLINT_LWC = 'eslint-lwc', @@ -58,6 +60,7 @@ export const AllowedEngineFilters = [ ENGINE.ESLINT_LWC, ENGINE.ESLINT_TYPESCRIPT, ENGINE.PMD, + ENGINE.PMD_APPEXCHANGE, ENGINE.RETIRE_JS, ENGINE.CPD, ENGINE.SFGE @@ -73,6 +76,7 @@ export const PathlessEngineFilters = [ ENGINE.ESLINT_LWC, ENGINE.ESLINT_TYPESCRIPT, ENGINE.PMD, + ENGINE.PMD_APPEXCHANGE, ENGINE.RETIRE_JS, ENGINE.SFGE, ENGINE.CPD @@ -129,3 +133,6 @@ export enum Severity { // Here, current dir __dirname = /sfdx-scanner/src export const PMD_LIB = path.join(__dirname, '..', 'dist', 'pmd', 'lib'); + +// Here, current dir __dirname = /sfdx-scanner/src +export const APPEXCHANGE_PMD_LIB = path.join(__dirname, '..', 'pmd-appexchange', 'lib'); diff --git a/src/ioc.config.ts b/src/ioc.config.ts index 129c28684..d60c2f252 100644 --- a/src/ioc.config.ts +++ b/src/ioc.config.ts @@ -10,7 +10,7 @@ import {CustomEslintEngine} from './lib/eslint/CustomEslintEngine'; import {RetireJsEngine} from './lib/retire-js/RetireJsEngine'; import {SfgeDfaEngine} from './lib/sfge/SfgeDfaEngine'; import {SfgePathlessEngine} from './lib/sfge/SfgePathlessEngine'; -import {CustomPmdEngine, PmdEngine} from './lib/pmd/PmdEngine'; +import {AppExchangePmdEngine, CustomPmdEngine, PmdEngine} from './lib/pmd/PmdEngine'; import LocalCatalog from './lib/services/LocalCatalog'; import {Config} from './lib/util/Config'; import {Services} from './Constants'; @@ -27,6 +27,7 @@ export function registerAll(): void { container.registerSingleton(Services.RuleManager, DefaultRuleManager); container.registerSingleton(Services.RuleEngine, PmdEngine); container.registerSingleton(Services.RuleEngine, CustomPmdEngine); + container.registerSingleton(Services.RuleEngine, AppExchangePmdEngine); container.registerSingleton(Services.RuleEngine, JavascriptEslintEngine); container.registerSingleton(Services.RuleEngine, LWCEslintEngine); container.registerSingleton(Services.RuleEngine, TypescriptEslintEngine); diff --git a/src/lib/pmd/PmdCatalogWrapper.ts b/src/lib/pmd/PmdCatalogWrapper.ts index ccf9756c9..46752ad0a 100644 --- a/src/lib/pmd/PmdCatalogWrapper.ts +++ b/src/lib/pmd/PmdCatalogWrapper.ts @@ -11,13 +11,19 @@ import {BundleName, getMessage} from "../../MessageCatalog"; const PMD_CATALOGER_LIB = path.join(__dirname, '..', '..', '..', 'dist', 'pmd-cataloger', 'lib'); const MAIN_CLASS = 'sfdc.sfdx.scanner.pmd.Main'; +type PmdCatalogWrapperOptions = PmdSupportOptions & { + catalogedEngineName: string; +}; + export class PmdCatalogWrapper extends PmdSupport { private logger: Logger; private initialized: boolean; private catalogFilePath: path.ParsedPath; + private readonly catalogedEngineName: string; - constructor(opts: PmdSupportOptions) { + constructor(opts: PmdCatalogWrapperOptions) { super(opts); + this.catalogedEngineName = opts.catalogedEngineName; } protected async init(): Promise { @@ -51,7 +57,7 @@ export class PmdCatalogWrapper extends PmdSupport { const languageArgs: string[] = this.buildLanguageArgs(); this.logger.trace(`Cataloger parameters have been built: ${JSON.stringify(languageArgs)}`); - const args = [`-DcatalogHome=${this.catalogFilePath.dir}`, `-DcatalogName=${this.catalogFilePath.base}`, '-cp', classpath, MAIN_CLASS, ...languageArgs]; + const args = [`-DcatalogHome=${this.catalogFilePath.dir}`, `-DcatalogName=${this.catalogFilePath.base}`, `-DcatalogedEngineName=${this.catalogedEngineName}`, '-cp', classpath, MAIN_CLASS, ...languageArgs]; this.logger.trace(`Preparing to execute PMD Cataloger with command: "${command}", args: "${JSON.stringify(args)}"`); return [command, args]; diff --git a/src/lib/pmd/PmdEngine.ts b/src/lib/pmd/PmdEngine.ts index 4059011f2..4006806b9 100644 --- a/src/lib/pmd/PmdEngine.ts +++ b/src/lib/pmd/PmdEngine.ts @@ -4,7 +4,7 @@ import {Controller} from '../../Controller'; import {Catalog, Rule, RuleGroup, RuleResult, RuleTarget, RuleViolation, TargetPattern} from '../../types'; import {AbstractRuleEngine} from '../services/RuleEngine'; import {Config} from '../util/Config'; -import {CUSTOM_CONFIG, ENGINE, EngineBase, HARDCODED_RULES, PMD_LIB, PMD_VERSION, Severity} from '../../Constants'; +import {APPEXCHANGE_PMD_LIB, PMD_APPEXCHANGE_VERSION, CUSTOM_CONFIG, ENGINE, EngineBase, HARDCODED_RULES, PMD_LIB, PMD_VERSION, Severity} from '../../Constants'; import {PmdCatalogWrapper} from './PmdCatalogWrapper'; import PmdWrapper from './PmdWrapper'; import {EVENTS, uxEvents} from "../ScannerEvents"; @@ -82,10 +82,6 @@ abstract class AbstractPmdEngine extends AbstractRuleEngine { protected eventCreator: EventCreator; private initialized: boolean; - getTargetPatterns(): Promise { - return this.config.getTargetPatterns(ENGINE.PMD); - } - public matchPath(path: string): boolean { // TODO implement this for realz return path != null; @@ -133,7 +129,7 @@ abstract class AbstractPmdEngine extends AbstractRuleEngine { this.initialized = true; } - protected async runInternal(selectedRules: string, targets: RuleTarget[], rulePathsByLanguage: Map>): Promise { + protected async runInternal(selectedRules: string, targets: RuleTarget[], rulePathsByLanguage: Map>, supplementalClasspath: string[] = []): Promise { try { const targetPaths: string[] = []; for (const target of targets) { @@ -148,7 +144,8 @@ abstract class AbstractPmdEngine extends AbstractRuleEngine { const stdout = await (await PmdWrapper.create({ targets: targetPaths, rules: selectedRules, - rulePathsByLanguage + rulePathsByLanguage, + supplementalClasspath })).execute(); const results = this.processStdOut(stdout); this.logger.trace(`Found ${results.length} for PMD`); @@ -430,10 +427,15 @@ export class PmdEngine extends AbstractPmdEngine { return PmdEngine.ENGINE_NAME; } + getTargetPatterns(): Promise { + return this.config.getTargetPatterns(ENGINE.PMD); + } + async getCatalog(): Promise { if (!this.catalogWrapper) { this.catalogWrapper = await PmdCatalogWrapper.create({ - rulePathsByLanguage: await (await _PmdRuleMapper.create({})).createStandardRuleMap() + rulePathsByLanguage: await (await _PmdRuleMapper.create({})).createStandardRuleMap(), + catalogedEngineName: PmdEngine.ENGINE_NAME }); } return this.catalogWrapper.getCatalog(); @@ -480,6 +482,11 @@ export class CustomPmdEngine extends AbstractPmdEngine { return CustomPmdEngine.THIS_ENGINE.valueOf(); } + getTargetPatterns(): Promise { + // The Custom variant shares the same target patterns as the standard variant. + return this.config.getTargetPatterns(ENGINE.PMD); + } + isEnabled(): Promise { return Promise.resolve(true); // Custom config will always be enabled } @@ -536,7 +543,64 @@ export class CustomPmdEngine extends AbstractPmdEngine { return configFile; } +} + +export class AppExchangePmdEngine extends AbstractPmdEngine { + private static readonly SUPPORTED_LANGUAGES = ['apex', 'html', 'javascript', 'visualforce', 'xml']; + private static ENGINE_ENUM = ENGINE.PMD_APPEXCHANGE; + public static ENGINE_NAME = AppExchangePmdEngine.ENGINE_ENUM.valueOf(); + private catalogWrapper: PmdCatalogWrapper; + + getName(): string { + return AppExchangePmdEngine.ENGINE_NAME; + } + + getTargetPatterns(): Promise { + return this.config.getTargetPatterns(ENGINE.PMD_APPEXCHANGE); + } + + async getCatalog(): Promise { + if (!this.catalogWrapper) { + this.catalogWrapper = await PmdCatalogWrapper.create({ + rulePathsByLanguage: this.createRuleMap(), + catalogedEngineName: AppExchangePmdEngine.ENGINE_NAME + }); + } + return this.catalogWrapper.getCatalog(); + } + shouldEngineRun( + ruleGroups: RuleGroup[], + rules: Rule[], + target: RuleTarget[], + engineOptions: Map): boolean { + // If this isn't a custom run, and there are rule groups for the engine to run, + // then we're good. + return !isCustomRun(engineOptions) && (ruleGroups.length > 0); + } + + isEngineRequested(filterValues: string[], engineOptions: Map): boolean { + // If the analyzer run isn't custom, then this engine counts as requested by default. + return !isCustomRun(engineOptions) && engineUtils.isFilterEmptyOrNameInFilter(this.getName(), filterValues); + } + + public async run(ruleGroups: RuleGroup[], rules: Rule[], targets: RuleTarget[], engineOptions: Map): Promise { + const selectedRules = ruleGroups.map(np => np.paths).join(','); + return await this.runInternal(selectedRules, targets, this.createRuleMap(), [`${APPEXCHANGE_PMD_LIB}/*`]); + } + + public isEnabled(): Promise { + return this.config.isEngineEnabled(AppExchangePmdEngine.ENGINE_ENUM); + } + + private createRuleMap(): Map> { + const rulePathsByLanguage = new Map>(); + for (const language of AppExchangePmdEngine.SUPPORTED_LANGUAGES) { + const jarPath = path.join(`${APPEXCHANGE_PMD_LIB}`, `sfca-pmd-${language}-${PMD_APPEXCHANGE_VERSION}.jar`); + rulePathsByLanguage.set(language, new Set([jarPath])); + } + return rulePathsByLanguage; + } } /** diff --git a/src/lib/pmd/PmdWrapper.ts b/src/lib/pmd/PmdWrapper.ts index 4b3525369..dc59c528e 100644 --- a/src/lib/pmd/PmdWrapper.ts +++ b/src/lib/pmd/PmdWrapper.ts @@ -11,11 +11,17 @@ const HEAP_SIZE = '-Xmx1024m'; type PmdWrapperOptions = PmdSupportOptions & { targets: string[]; rules: string; + /** + * Any extra files that need to be added to the classpath, NOT including files that declare rules (since those files + * are handled elsewhere). + */ + supplementalClasspath: string[]; }; export default class PmdWrapper extends PmdSupport { private targets: string[]; - private rules: string; + private readonly rules: string; + private supplementalClasspath: string[]; private logger: Logger; private initialized: boolean; @@ -23,6 +29,7 @@ export default class PmdWrapper extends PmdSupport { super(opts); this.targets = opts.targets; this.rules = opts.rules; + this.supplementalClasspath = opts.supplementalClasspath; } protected async init(): Promise { @@ -45,7 +52,7 @@ export default class PmdWrapper extends PmdSupport { // 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 = [`${PMD_LIB}/*`, ...this.buildSharedClasspath()].join(path.delimiter); + const classpath = [...this.supplementalClasspath, `${PMD_LIB}/*`, ...this.buildSharedClasspath()].join(path.delimiter); // Operating systems impose limits on the maximum length of a command line invocation. This can be problematic // when scanning a large number of files. Store the list of files to scan in a temp file. Pass the location // of the temp file to PMD. The temp file is cleaned up when the process exits. diff --git a/src/lib/util/Config.ts b/src/lib/util/Config.ts index e56b842ae..7ed13c8d2 100644 --- a/src/lib/util/Config.ts +++ b/src/lib/util/Config.ts @@ -27,6 +27,26 @@ export type EngineConfigContent = { minimumTokens?: number; } +/** + * An array of file extensions that can theoretically correspond to XML files when scanning a Salesforce Managed Package. + */ +const APPEXCHANGE_XML_EXTENSIONS = [ + "app", "authprovider", "bot", "brandingSet", "cachePartition", "callCenter", "communityTemplateDefinition", + "communityThemeDefinition", "connectedApp", "ConversationVendorInformation", "corsWhitelistOrigin", + "cspTrustedSite", "customHelpMenuSection", "customPermission", "dashboard", "dataConnectorIngestApi", + "dataPackageKitDefinition", "DataPackageKitObject", "dataSource", "dataSourceBundleDefinition", + "dataSourceObject", "dataSrcDataModelFieldMap", "dataStreamDefinition", "dataStreamTemplate", + "duplicateRule", "externalDataConnector", "featureParameterBoolean", "featureParameterInteger", + "flexipage", "flow", "globalValueSet", "globalValueSetTranslation", "homePageComponent", + "homePageLayout", "indx", "labels", "layout", "letter", "lightningBolt", "lightningExperienceTheme", + "marketingappextension", "matchingRule", "md", "messageChannel", "mktDataTranObject", "mlDomain", + "namedCredential", "navigationMenu", "notiftype", "object", "objectSourceTargetMap", "objectTranslation", + "pathAssistant", "permissionset", "permissionsetgroup", "profile", "prompt", "quickAction", "remoteSite", + "report", "reportType", "sharingSet", "snapshot", "tab", "translation", "wapp", "wds", "weblink", "workflow", + "xmd" +]; + + const DEFAULT_CONFIG: ConfigContent = { // It's typically bad practice to use `require` instead of `import`, but the former is much more straightforward // in this case. @@ -42,6 +62,21 @@ const DEFAULT_CONFIG: ConfigContent = { supportedLanguages: ['apex', 'vf'], disabled: false }, + { + name: ENGINE.PMD_APPEXCHANGE, + targetPatterns: [ + // By default, the App Exchange PMD variant targets all the same patterns as the base PMD engine. + "**/*.cls","**/*.trigger","**/*.java","**/*.page","**/*.component","**/*.xml", + "!**/node_modules/**", + // It also targets JS files, since there are JS-specific rules. + "**/*.js", + // It also targets all the various extensions that, in a managed package, can represent XML files. + ...(APPEXCHANGE_XML_EXTENSIONS.map(s => `**/*.${s}`)), + ], + // The App Exchange PMD variant is disabled by default, since it's only relevant to users submitting + // for security review. + disabled: true + }, { name: ENGINE.ESLINT, targetPatterns: [ diff --git a/test/commands/scanner/rule/list.test.ts b/test/commands/scanner/rule/list.test.ts index e976b085d..19092aa3b 100644 --- a/test/commands/scanner/rule/list.test.ts +++ b/test/commands/scanner/rule/list.test.ts @@ -222,7 +222,7 @@ describe('scanner rule list', () => { it('Filtering by a single engine returns only rules applied to that engine', () => { const output = runCommand(`scanner rule list --engine ${ENGINE.PMD} --json`); // Count how many rules in the catalog fit the criteria. - const targetRuleCount = getCatalogJson().rules.filter(rule => rule.engine.includes(ENGINE.PMD)).length; + const targetRuleCount = getCatalogJson().rules.filter(rule => rule.engine === (ENGINE.PMD)).length; // Parse the output back into a JSON and make sure it has the right number of rules. const outputJson = output.jsonOutput; diff --git a/test/lib/Controller.test.ts b/test/lib/Controller.test.ts index e49c56f98..da336ffb7 100644 --- a/test/lib/Controller.test.ts +++ b/test/lib/Controller.test.ts @@ -18,12 +18,13 @@ describe('Controller.ts tests', () => { const engines: RuleEngine[] = await Controller.getAllEngines(); const names: string[] = engines.map(e => e.constructor.name); - expect(engines.length, names + '').to.equal(10); + expect(engines.length, names + '').to.equal(11); expect(names).to.contain('JavascriptEslintEngine'); expect(names).to.contain('LWCEslintEngine'); expect(names).to.contain('TypescriptEslintEngine'); expect(names).to.contain('CustomEslintEngine'); expect(names).to.contain('PmdEngine'); + expect(names).to.contain('AppExchangePmdEngine'); expect(names).to.contain('CustomPmdEngine'); expect(names).to.contain('RetireJsEngine'); expect(names).to.contain('CpdEngine'); @@ -117,11 +118,12 @@ describe('Controller.ts tests', () => { const engines: RuleEngine[] = await Controller.getFilteredEngines([]); const names: string[] = engines.map(e => e.constructor.name); - expect(engines.length).to.equal(6); + expect(engines.length).to.equal(7); expect(names).to.contain('JavascriptEslintEngine'); expect(names).to.contain('LWCEslintEngine'); expect(names).to.contain('TypescriptEslintEngine'); expect(names).to.contain('PmdEngine'); + expect(names).to.contain('AppExchangePmdEngine'); expect(names).to.contain('RetireJsEngine'); expect(names).to.contain('SfgeDfaEngine'); }) @@ -152,7 +154,7 @@ describe('Controller.ts tests', () => { await Controller.getFilteredEngines(['invalid-engine']); fail('getFilteredEngines should have thrown'); } catch (e) { - expect(e.message).to.equal(`The filter doesn't match any engines. Filter 'invalid-engine'. Engines: cpd, eslint, eslint-lwc, eslint-typescript, pmd, retire-js, sfge.`); + expect(e.message).to.equal(`The filter doesn't match any engines. Filter 'invalid-engine'. Engines: cpd, eslint, eslint-lwc, eslint-typescript, pmd, pmd-appexchange, retire-js, sfge.`); } }); }); diff --git a/test/lib/pmd/PmdCatalogWrapper.test.ts b/test/lib/pmd/PmdCatalogWrapper.test.ts index ecd2d92d2..885314dbe 100644 --- a/test/lib/pmd/PmdCatalogWrapper.test.ts +++ b/test/lib/pmd/PmdCatalogWrapper.test.ts @@ -32,12 +32,14 @@ describe('PmdCatalogWrapper', () => { const expectedParamList = [ `-DcatalogHome=`, '-DcatalogName=', + '-DcatalogedEngineName=', '-cp', thePath, 'sfdc.sfdx.scanner.pmd.Main']; const target = await TestablePmdCatalogWrapper.create({ - rulePathsByLanguage: new Map>() + rulePathsByLanguage: new Map>(), + catalogedEngineName: 'TestVariant' }); const params = (await target.buildCommandArray())[1]; From 674eb95feac575ca20cfabacc84bbd1d2ddf18c4 Mon Sep 17 00:00:00 2001 From: Josh Feingold Date: Thu, 28 Dec 2023 12:21:24 -0600 Subject: [PATCH 04/10] @W-13222948@: Updated JARs and added docs. --- pmd-appexchange/docs/AvoidApiSessionId.md | 18 +++++++ .../docs/AvoidAuraWithLockerDisabled.md | 18 +++++++ ...ingSystemResetPasswordWithEmailTemplate.md | 18 +++++++ pmd-appexchange/docs/AvoidChangeProtection.md | 18 +++++++ .../docs/AvoidChangeProtectionUnprotected.md | 18 +++++++ .../docs/AvoidHardcodedCredentials.md | 47 ++++++++++++++++++ .../docs/AvoidJavaScriptCustomRule.md | 18 +++++++ .../docs/AvoidJavaScriptHomePageComponent.md | 18 +++++++ .../docs/AvoidJavaScriptWeblink.md | 18 +++++++ .../docs/AvoidJsLinksInCustomObject.md | 18 +++++++ .../docs/AvoidJsLinksInWebLinks.md | 18 +++++++ pmd-appexchange/docs/AvoidLmcIsExposedTrue.md | 18 +++++++ .../docs/AvoidLwcBubblesComposedTrue.md | 18 +++++++ .../docs/AvoidSystemModeInFlows.md | 18 +++++++ .../AvoidUnauthorizedApiSessionIdInApex.md | 18 +++++++ .../AvoidUnauthorizedApiSessionIdInFlows.md | 18 +++++++ ...voidUnauthorizedApiSessionIdVisualforce.md | 18 +++++++ .../AvoidUnauthorizedGetSessionIdInApex.md | 18 +++++++ ...idUnauthorizedGetSessionIdInVisualforce.md | 18 +++++++ .../docs/AvoidUnsafeSystemMovePassword.md | 18 +++++++ .../docs/AvoidUnsafeSystemResetPassword.md | 18 +++++++ .../docs/AvoidUnsafeSystemSetPassword.md | 18 +++++++ .../AvoidWithoutSharingInRestApiController.md | 18 +++++++ .../docs/LimitConnectedAppScope.md | 18 +++++++ .../docs/LimitPermissionSetAssignment.md | 18 +++++++ .../docs/LoadJavaScriptHtmlScript.md | 26 ++++++++++ .../docs/LoadJavaScriptIncludeScript.md | 26 ++++++++++ pmd-appexchange/docs/ProtectSensitiveData.md | 45 +++++++++++++++++ .../docs/UpgradeLwcLockerSecuritySupport.md | 18 +++++++ pmd-appexchange/docs/UseHttpsCallbackUrl.md | 18 +++++++ pmd-appexchange/docs/UseLwcDomManual.md | 31 ++++++++++++ .../ValidateCrudFlsEmailMessageWhoIdWhatId.md | 18 +++++++ pmd-appexchange/lib/pmd-xml-sf-0.0.1.jar | Bin 2931 -> 2931 bytes pmd-appexchange/lib/sfca-pmd-apex-0.12.jar | Bin 9140 -> 8217 bytes pmd-appexchange/lib/sfca-pmd-html-0.12.jar | Bin 1946 -> 1920 bytes .../lib/sfca-pmd-javascript-0.12.jar | Bin 1873 -> 1951 bytes .../lib/sfca-pmd-visualforce-0.12.jar | Bin 7568 -> 7618 bytes pmd-appexchange/lib/sfca-pmd-xml-0.12.jar | Bin 4153 -> 3986 bytes 38 files changed, 661 insertions(+) create mode 100644 pmd-appexchange/docs/AvoidApiSessionId.md create mode 100644 pmd-appexchange/docs/AvoidAuraWithLockerDisabled.md create mode 100644 pmd-appexchange/docs/AvoidCallingSystemResetPasswordWithEmailTemplate.md create mode 100644 pmd-appexchange/docs/AvoidChangeProtection.md create mode 100644 pmd-appexchange/docs/AvoidChangeProtectionUnprotected.md create mode 100644 pmd-appexchange/docs/AvoidHardcodedCredentials.md create mode 100644 pmd-appexchange/docs/AvoidJavaScriptCustomRule.md create mode 100644 pmd-appexchange/docs/AvoidJavaScriptHomePageComponent.md create mode 100644 pmd-appexchange/docs/AvoidJavaScriptWeblink.md create mode 100644 pmd-appexchange/docs/AvoidJsLinksInCustomObject.md create mode 100644 pmd-appexchange/docs/AvoidJsLinksInWebLinks.md create mode 100644 pmd-appexchange/docs/AvoidLmcIsExposedTrue.md create mode 100644 pmd-appexchange/docs/AvoidLwcBubblesComposedTrue.md create mode 100644 pmd-appexchange/docs/AvoidSystemModeInFlows.md create mode 100644 pmd-appexchange/docs/AvoidUnauthorizedApiSessionIdInApex.md create mode 100644 pmd-appexchange/docs/AvoidUnauthorizedApiSessionIdInFlows.md create mode 100644 pmd-appexchange/docs/AvoidUnauthorizedApiSessionIdVisualforce.md create mode 100644 pmd-appexchange/docs/AvoidUnauthorizedGetSessionIdInApex.md create mode 100644 pmd-appexchange/docs/AvoidUnauthorizedGetSessionIdInVisualforce.md create mode 100644 pmd-appexchange/docs/AvoidUnsafeSystemMovePassword.md create mode 100644 pmd-appexchange/docs/AvoidUnsafeSystemResetPassword.md create mode 100644 pmd-appexchange/docs/AvoidUnsafeSystemSetPassword.md create mode 100644 pmd-appexchange/docs/AvoidWithoutSharingInRestApiController.md create mode 100644 pmd-appexchange/docs/LimitConnectedAppScope.md create mode 100644 pmd-appexchange/docs/LimitPermissionSetAssignment.md create mode 100644 pmd-appexchange/docs/LoadJavaScriptHtmlScript.md create mode 100644 pmd-appexchange/docs/LoadJavaScriptIncludeScript.md create mode 100644 pmd-appexchange/docs/ProtectSensitiveData.md create mode 100644 pmd-appexchange/docs/UpgradeLwcLockerSecuritySupport.md create mode 100644 pmd-appexchange/docs/UseHttpsCallbackUrl.md create mode 100644 pmd-appexchange/docs/UseLwcDomManual.md create mode 100644 pmd-appexchange/docs/ValidateCrudFlsEmailMessageWhoIdWhatId.md diff --git a/pmd-appexchange/docs/AvoidApiSessionId.md b/pmd-appexchange/docs/AvoidApiSessionId.md new file mode 100644 index 000000000..0f4a66bd4 --- /dev/null +++ b/pmd-appexchange/docs/AvoidApiSessionId.md @@ -0,0 +1,18 @@ +AvoidApiSessionId[](#avoidapisessionid) +------------------------------------------------------------------------------------------------------------------------------------------------------ + +**Violation:** + + Session ID use is not approved. + + +**Priority:** High (2) + +**Description:** + + Detects use of Api.Session_ID to retrieve a session ID. + +**Example(s):** + + + diff --git a/pmd-appexchange/docs/AvoidAuraWithLockerDisabled.md b/pmd-appexchange/docs/AvoidAuraWithLockerDisabled.md new file mode 100644 index 000000000..487d9fd31 --- /dev/null +++ b/pmd-appexchange/docs/AvoidAuraWithLockerDisabled.md @@ -0,0 +1,18 @@ +AvoidAuraWithLockerDisabled[](#avoidaurawithlockerdisabled) +------------------------------------------------------------------------------------------------------------------------------------------------------ + +**Violation:** + + To enable Lightning Locker, update the apiVersion to version 40 or greater. + + +**Priority:** Critical (1) + +**Description:** + + Detects use of API versions with Lightning Locker disabled in Aura components. Use API version 40 or greater. + +**Example(s):** + + + diff --git a/pmd-appexchange/docs/AvoidCallingSystemResetPasswordWithEmailTemplate.md b/pmd-appexchange/docs/AvoidCallingSystemResetPasswordWithEmailTemplate.md new file mode 100644 index 000000000..d522f0b12 --- /dev/null +++ b/pmd-appexchange/docs/AvoidCallingSystemResetPasswordWithEmailTemplate.md @@ -0,0 +1,18 @@ +AvoidCallingSystemResetPasswordWithEmailTemplate[](#avoidcallingsystemresetpasswordwithemailtemplate) +------------------------------------------------------------------------------------------------------------------------------------------------------ + +**Violation:** + + Before calling System.resetPasswordWithEmailTemplate(), perform the necessary authorization checks. + + +**Priority:** Critical (1) + +**Description:** + + Detects where System.resetPasswordWithEmailTemplate() exists in Apex code. Use this method with caution. + +**Example(s):** + + + diff --git a/pmd-appexchange/docs/AvoidChangeProtection.md b/pmd-appexchange/docs/AvoidChangeProtection.md new file mode 100644 index 000000000..ea3644f55 --- /dev/null +++ b/pmd-appexchange/docs/AvoidChangeProtection.md @@ -0,0 +1,18 @@ +AvoidChangeProtection[](#avoidchangeprotection) +------------------------------------------------------------------------------------------------------------------------------------------------------ + +**Violation:** + + Update your code to avoid using FeatureManagement.changeProtection. + + +**Priority:** High (2) + +**Description:** + + Detects potential misuse of FeatureManagement.changeProtection. + +**Example(s):** + + + diff --git a/pmd-appexchange/docs/AvoidChangeProtectionUnprotected.md b/pmd-appexchange/docs/AvoidChangeProtectionUnprotected.md new file mode 100644 index 000000000..3abed876f --- /dev/null +++ b/pmd-appexchange/docs/AvoidChangeProtectionUnprotected.md @@ -0,0 +1,18 @@ +AvoidChangeProtectionUnprotected[](#avoidchangeprotectionunprotected) +------------------------------------------------------------------------------------------------------------------------------------------------------ + +**Violation:** + + Update your code to avoid using FeatureManagement.changeProtection called by an UnProtected argument. + + +**Priority:** Critical (1) + +**Description:** + + Detects potential misuse of FeatureManagement.changeProtection. + +**Example(s):** + + + diff --git a/pmd-appexchange/docs/AvoidHardcodedCredentials.md b/pmd-appexchange/docs/AvoidHardcodedCredentials.md new file mode 100644 index 000000000..79ef09293 --- /dev/null +++ b/pmd-appexchange/docs/AvoidHardcodedCredentials.md @@ -0,0 +1,47 @@ +AvoidHardcodedCredentials[](#avoidhardcodedcredentials) +------------------------------------------------------------------------------------------------------------------------------------------------------ + +**Violation:** + + Remove hard-coded credentials from source code. + + +**Priority:** Medium (3) + +**Description:** + + Identifies hard-coded credentials in source code that must be protected using Protected Custom metadata or Protected Custom settings. + +**Example(s):** + + Correct Method + +``` + + + List + false + + Protected + +``` + +Incorrect Method + +``` +public with sharing class test3 { + public test3() { + String key = 'supersecurepassword'; + HttpRequest req = new HttpRequest(); + req.setEndpoint('https://www.example.com/test?APIKEY='+key); + req.setMethod('GET'); + Http http = new Http(); + HTTPResponse res = http.send(req); + return res.getBody(); + } +``` + + + + + diff --git a/pmd-appexchange/docs/AvoidJavaScriptCustomRule.md b/pmd-appexchange/docs/AvoidJavaScriptCustomRule.md new file mode 100644 index 000000000..0456cbb7f --- /dev/null +++ b/pmd-appexchange/docs/AvoidJavaScriptCustomRule.md @@ -0,0 +1,18 @@ +AvoidJavaScriptCustomRule[](#avoidjavascriptcustomrule) +------------------------------------------------------------------------------------------------------------------------------------------------------ + +**Violation:** + + Avoid using JavaScript to execute custom button actions. + + +**Priority:** High (2) + +**Description:** + + Detects use of custom JavaScript actions in custom rules. + +**Example(s):** + + + diff --git a/pmd-appexchange/docs/AvoidJavaScriptHomePageComponent.md b/pmd-appexchange/docs/AvoidJavaScriptHomePageComponent.md new file mode 100644 index 000000000..ec8373665 --- /dev/null +++ b/pmd-appexchange/docs/AvoidJavaScriptHomePageComponent.md @@ -0,0 +1,18 @@ +AvoidJavaScriptHomePageComponent[](#avoidjavascripthomepagecomponent) +------------------------------------------------------------------------------------------------------------------------------------------------------ + +**Violation:** + + Avoid JavaScript in a home page component body. + + +**Priority:** High (2) + +**Description:** + + Detects use of custom JavaScript actions in home page components. + +**Example(s):** + + + diff --git a/pmd-appexchange/docs/AvoidJavaScriptWeblink.md b/pmd-appexchange/docs/AvoidJavaScriptWeblink.md new file mode 100644 index 000000000..fee204a03 --- /dev/null +++ b/pmd-appexchange/docs/AvoidJavaScriptWeblink.md @@ -0,0 +1,18 @@ +AvoidJavaScriptWeblink[](#avoidjavascriptweblink) +------------------------------------------------------------------------------------------------------------------------------------------------------ + +**Violation:** + + Avoid using JavaScript in web links. + + +**Priority:** High (2) + +**Description:** + + Detects use of custom JavaScript actions in web links. + +**Example(s):** + + + diff --git a/pmd-appexchange/docs/AvoidJsLinksInCustomObject.md b/pmd-appexchange/docs/AvoidJsLinksInCustomObject.md new file mode 100644 index 000000000..0be776671 --- /dev/null +++ b/pmd-appexchange/docs/AvoidJsLinksInCustomObject.md @@ -0,0 +1,18 @@ +AvoidJsLinksInCustomObject[](#avoidjslinksincustomobject) +------------------------------------------------------------------------------------------------------------------------------------------------------ + +**Violation:** + + Avoid clickable JavaScript-style URLs. + + +**Priority:** Critical (1) + +**Description:** + + Detects instances of JavaScript-style URLs (javascript:) in Salesforce DOM components, such as web links and buttons. Avoid JavaScript-style URLs in managed packages. + +**Example(s):** + + + diff --git a/pmd-appexchange/docs/AvoidJsLinksInWebLinks.md b/pmd-appexchange/docs/AvoidJsLinksInWebLinks.md new file mode 100644 index 000000000..90168ffe5 --- /dev/null +++ b/pmd-appexchange/docs/AvoidJsLinksInWebLinks.md @@ -0,0 +1,18 @@ +AvoidJsLinksInWebLinks[](#avoidjslinksinweblinks) +------------------------------------------------------------------------------------------------------------------------------------------------------ + +**Violation:** + + Avoid clickable JavaScript-style URLs. + + +**Priority:** Critical (1) + +**Description:** + + Detects instances of JavaScript-style URLs (javascript:) in Salesforce DOM components, such as web links and buttons. Avoid JavaScript-style URLs in managed packages. + +**Example(s):** + + + diff --git a/pmd-appexchange/docs/AvoidLmcIsExposedTrue.md b/pmd-appexchange/docs/AvoidLmcIsExposedTrue.md new file mode 100644 index 000000000..c83d05a4a --- /dev/null +++ b/pmd-appexchange/docs/AvoidLmcIsExposedTrue.md @@ -0,0 +1,18 @@ +AvoidLmcIsExposedTrue[](#avoidlmcisexposedtrue) +------------------------------------------------------------------------------------------------------------------------------------------------------ + +**Violation:** + + Use Lightning Message Channel with isExposed set to false. + + +**Priority:** High (2) + +**Description:** + + Detects a Lightning Message Channel with isExposed=true, which isn’t allowed in managed packages. + +**Example(s):** + + + diff --git a/pmd-appexchange/docs/AvoidLwcBubblesComposedTrue.md b/pmd-appexchange/docs/AvoidLwcBubblesComposedTrue.md new file mode 100644 index 000000000..d3d7d675c --- /dev/null +++ b/pmd-appexchange/docs/AvoidLwcBubblesComposedTrue.md @@ -0,0 +1,18 @@ +AvoidLwcBubblesComposedTrue[](#avoidlwcbubblescomposedtrue) +------------------------------------------------------------------------------------------------------------------------------------------------------ + +**Violation:** + + Avoid setting both Lightning Web component bubbles and composed=true at the same time. + + +**Priority:** Medium (3) + +**Description:** + + Detects Lightning Web Component event configurations where bubbles and composed are both set to true. To avoid sharing sensitive information unintentionally, use this configuration with caution. + +**Example(s):** + + + diff --git a/pmd-appexchange/docs/AvoidSystemModeInFlows.md b/pmd-appexchange/docs/AvoidSystemModeInFlows.md new file mode 100644 index 000000000..78d97092d --- /dev/null +++ b/pmd-appexchange/docs/AvoidSystemModeInFlows.md @@ -0,0 +1,18 @@ +AvoidSystemModeInFlows[](#avoidsystemmodeinflows) +------------------------------------------------------------------------------------------------------------------------------------------------------ + +**Violation:** + + Reconfigure to avoid running flows in system mode. + + +**Priority:** Medium (3) + +**Description:** + + Detects where default mode must be used in flows instead of system mode. + +**Example(s):** + + + diff --git a/pmd-appexchange/docs/AvoidUnauthorizedApiSessionIdInApex.md b/pmd-appexchange/docs/AvoidUnauthorizedApiSessionIdInApex.md new file mode 100644 index 000000000..b3b906be8 --- /dev/null +++ b/pmd-appexchange/docs/AvoidUnauthorizedApiSessionIdInApex.md @@ -0,0 +1,18 @@ +AvoidUnauthorizedApiSessionIdInApex[](#avoidunauthorizedapisessionidinapex) +------------------------------------------------------------------------------------------------------------------------------------------------------ + +**Violation:** + + Use of API.Session_ID might not be authorized. + + +**Priority:** High (2) + +**Description:** + + Detects use of Api.Session_ID to retrieve a session ID. + +**Example(s):** + + + diff --git a/pmd-appexchange/docs/AvoidUnauthorizedApiSessionIdInFlows.md b/pmd-appexchange/docs/AvoidUnauthorizedApiSessionIdInFlows.md new file mode 100644 index 000000000..58c47e803 --- /dev/null +++ b/pmd-appexchange/docs/AvoidUnauthorizedApiSessionIdInFlows.md @@ -0,0 +1,18 @@ +AvoidUnauthorizedApiSessionIdInFlows[](#avoidunauthorizedapisessionidinflows) +------------------------------------------------------------------------------------------------------------------------------------------------------ + +**Violation:** + + $Api.Session_ID usage is not approved. + + +**Priority:** High (2) + +**Description:** + + Detects use of session ID in SOAP API calls in flows. + +**Example(s):** + + + diff --git a/pmd-appexchange/docs/AvoidUnauthorizedApiSessionIdVisualforce.md b/pmd-appexchange/docs/AvoidUnauthorizedApiSessionIdVisualforce.md new file mode 100644 index 000000000..b65146a24 --- /dev/null +++ b/pmd-appexchange/docs/AvoidUnauthorizedApiSessionIdVisualforce.md @@ -0,0 +1,18 @@ +AvoidUnauthorizedApiSessionIdVisualforce[](#avoidunauthorizedapisessionidvisualforce) +------------------------------------------------------------------------------------------------------------------------------------------------------ + +**Violation:** + + Retrieval of session ID using API.Session_ID is not authorized. + + +**Priority:** Medium (3) + +**Description:** + + Detects use of Api.Session_ID to retrieve a session ID in Visualforce code. + +**Example(s):** + + + diff --git a/pmd-appexchange/docs/AvoidUnauthorizedGetSessionIdInApex.md b/pmd-appexchange/docs/AvoidUnauthorizedGetSessionIdInApex.md new file mode 100644 index 000000000..b389d7e97 --- /dev/null +++ b/pmd-appexchange/docs/AvoidUnauthorizedGetSessionIdInApex.md @@ -0,0 +1,18 @@ +AvoidUnauthorizedGetSessionIdInApex[](#avoidunauthorizedgetsessionidinapex) +------------------------------------------------------------------------------------------------------------------------------------------------------ + +**Violation:** + + Use of UserInfo.getSessionId might not be authorized. + + +**Priority:** Medium (3) + +**Description:** + + Detects use of UserInfo.getSessionId() to retrieve a session ID. + +**Example(s):** + + + diff --git a/pmd-appexchange/docs/AvoidUnauthorizedGetSessionIdInVisualforce.md b/pmd-appexchange/docs/AvoidUnauthorizedGetSessionIdInVisualforce.md new file mode 100644 index 000000000..cabaaa7d3 --- /dev/null +++ b/pmd-appexchange/docs/AvoidUnauthorizedGetSessionIdInVisualforce.md @@ -0,0 +1,18 @@ +AvoidUnauthorizedGetSessionIdInVisualforce[](#avoidunauthorizedgetsessionidinvisualforce) +------------------------------------------------------------------------------------------------------------------------------------------------------ + +**Violation:** + + Use of session ID with GETSESSIONID is not authorized. + + +**Priority:** High (2) + +**Description:** + + Detects use of GETSESSIONID() to retrieve a session ID in Visualforce code. + +**Example(s):** + + + diff --git a/pmd-appexchange/docs/AvoidUnsafeSystemMovePassword.md b/pmd-appexchange/docs/AvoidUnsafeSystemMovePassword.md new file mode 100644 index 000000000..5e396f3c0 --- /dev/null +++ b/pmd-appexchange/docs/AvoidUnsafeSystemMovePassword.md @@ -0,0 +1,18 @@ +AvoidUnsafeSystemMovePassword[](#avoidunsafesystemmovepassword) +------------------------------------------------------------------------------------------------------------------------------------------------------ + +**Violation:** + + Before calling System.movePassword(), perform the necessary authorization checks. + + +**Priority:** Critical (1) + +**Description:** + + Detects where System.movePassword() is used in Apex code. Use this method with caution. + +**Example(s):** + + + diff --git a/pmd-appexchange/docs/AvoidUnsafeSystemResetPassword.md b/pmd-appexchange/docs/AvoidUnsafeSystemResetPassword.md new file mode 100644 index 000000000..aacb724c8 --- /dev/null +++ b/pmd-appexchange/docs/AvoidUnsafeSystemResetPassword.md @@ -0,0 +1,18 @@ +AvoidUnsafeSystemResetPassword[](#avoidunsafesystemresetpassword) +------------------------------------------------------------------------------------------------------------------------------------------------------ + +**Violation:** + + Before calling System.resetPassword(), perform the necessary authorization checks. + + +**Priority:** Critical (1) + +**Description:** + + Detects where System.resetPassword() exists in Apex code. Use this method with caution. + +**Example(s):** + + + diff --git a/pmd-appexchange/docs/AvoidUnsafeSystemSetPassword.md b/pmd-appexchange/docs/AvoidUnsafeSystemSetPassword.md new file mode 100644 index 000000000..c7497e038 --- /dev/null +++ b/pmd-appexchange/docs/AvoidUnsafeSystemSetPassword.md @@ -0,0 +1,18 @@ +AvoidUnsafeSystemSetPassword[](#avoidunsafesystemsetpassword) +------------------------------------------------------------------------------------------------------------------------------------------------------ + +**Violation:** + + Before calling System.setPassword() in Apex, perform necessary authorization checks. + + +**Priority:** Critical (1) + +**Description:** + + Detects where System.setPassword() exists in Apex code. Use this method with caution. + +**Example(s):** + + + diff --git a/pmd-appexchange/docs/AvoidWithoutSharingInRestApiController.md b/pmd-appexchange/docs/AvoidWithoutSharingInRestApiController.md new file mode 100644 index 000000000..f5243a8ad --- /dev/null +++ b/pmd-appexchange/docs/AvoidWithoutSharingInRestApiController.md @@ -0,0 +1,18 @@ +AvoidWithoutSharingInRestApiController[](#avoidwithoutsharinginrestapicontroller) +------------------------------------------------------------------------------------------------------------------------------------------------------ + +**Violation:** + + Use "with sharing" in Apex classes with the @RestResource annotation. + + +**Priority:** High (2) + +**Description:** + + Detects use of "without sharing" in an Apex class exposed with the @RestResource annotation. + +**Example(s):** + + + diff --git a/pmd-appexchange/docs/LimitConnectedAppScope.md b/pmd-appexchange/docs/LimitConnectedAppScope.md new file mode 100644 index 000000000..b1aa409c8 --- /dev/null +++ b/pmd-appexchange/docs/LimitConnectedAppScope.md @@ -0,0 +1,18 @@ +LimitConnectedAppScope[](#limitconnectedappscope) +------------------------------------------------------------------------------------------------------------------------------------------------------ + +**Violation:** + + Update the connected app request to limited scope instead of full scope. + + +**Priority:** Medium (3) + +**Description:** + + Detects if a connected app uses full scope. Explain this use case in your AppExchange security review submission. + +**Example(s):** + + + diff --git a/pmd-appexchange/docs/LimitPermissionSetAssignment.md b/pmd-appexchange/docs/LimitPermissionSetAssignment.md new file mode 100644 index 000000000..39a964dd0 --- /dev/null +++ b/pmd-appexchange/docs/LimitPermissionSetAssignment.md @@ -0,0 +1,18 @@ +LimitPermissionSetAssignment[](#limitpermissionsetassignment) +------------------------------------------------------------------------------------------------------------------------------------------------------ + +**Violation:** + + Ensure that DML operations against PermissionSetAssignment use trusted input. + + +**Priority:** High (2) + +**Description:** + + Detects usage of PermissionSetAssignment. Use caution when allowing DML operations against the PermissionSetAssignment object. + +**Example(s):** + + + diff --git a/pmd-appexchange/docs/LoadJavaScriptHtmlScript.md b/pmd-appexchange/docs/LoadJavaScriptHtmlScript.md new file mode 100644 index 000000000..0d51761b2 --- /dev/null +++ b/pmd-appexchange/docs/LoadJavaScriptHtmlScript.md @@ -0,0 +1,26 @@ +LoadJavaScriptHtmlScript[](#loadjavascripthtmlscript) +------------------------------------------------------------------------------------------------------------------------------------------------------ + +**Violation:** + + Load JavaScript only from static resources. + + +**Priority:** High (2) + +**Description:** + + Determines HTML script locations where JavaScript code must be loaded as static resources. + +**Example(s):** + + + +``` +