Skip to content

Commit 64226c5

Browse files
authored
CHANGE: @W-18201387@ Template changes to support scaffolding (#251)
1 parent d3e4d1d commit 64226c5

File tree

17 files changed

+510
-3
lines changed

17 files changed

+510
-3
lines changed

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
# Salesforce Code Analyzer - Core Module
22

3-
The code in repository is currently for internal use only while we build the next generate Salesforce Code Analyzer.
3+
The code in repository is currently for internal use only while we build the next Salesforce Code Analyzer.
44

5-
See instead:
5+
See our external products:
66
* [sfdx-scanner](https://github.com/forcedotcom/sfdx-scanner)
7-
* [sfdx-code-analyzer-vscode](https://github.com/forcedotcom/sfdx-code-analyzer-vscode)
7+
* [sfdx-code-analyzer-vscode](https://github.com/forcedotcom/sfdx-code-analyzer-vscode)
8+
9+
# Adding Additional Engines
10+
11+
If you are an internal team looking to add Engine capabilities to Salesforce Code Analyzer, refer to the [ENGINE-TEMPLATE READ.ME](/packages/ENGINE-TEMPLATE/README.md).

package-lock.json

Lines changed: 41 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/ENGINE-TEMPLATE/LICENSE

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
BSD 3-Clause License
2+
3+
Copyright (c) 2025, Salesforce.com, Inc.
4+
All rights reserved.
5+
6+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
7+
8+
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
9+
10+
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
11+
12+
* Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
13+
14+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

packages/ENGINE-TEMPLATE/README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Adding New Engines
2+
3+
The [code-analyzer-core](../code-analyzer-core/) package has the capability to dynamically load any and all engines that live in this monorepo, based on both available engines and JIT user configurations. If you are an internal team looking to add plugin configuration that can be leveraged through the [code-analyzer Salesforce CLI plugin](https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_reference.meta/sfdx_cli_reference/cli_reference_code-analyzer_commands_unified.htm) and [Salesforce Code Analyzer VS Code Extension](https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/guide/analyze-vscode.html), this is where you should start.
4+
5+
6+
## Start with the Scaffolding
7+
8+
1. Copy This Template
9+
10+
Copy this entire template folder, and rename the directory to `code-analyzer-x-engine`, where `x` will be the name of your engine. For example, the `code-analyzer-sfge-engine` has `sfge` as the engine name for the capabilities referred to as the "Salesforce Graph Engine". Engine names should be lowercased and succinct. Engine names are subject to Salesforce Code Analyzer team approval.
11+
12+
2. Add Dependencies
13+
14+
Add any dependencies you might need for external rule retrieval or other functionality. Important to note in the included dependencies is the code analyzer engine api, which is also published from this monorepo:
15+
16+
```
17+
"@salesforce/code-analyzer-engine-api": "<MUST_MATCH_ENGINE_API_VERSION>"
18+
```
19+
20+
Use the value provided in the template today, and do not try to modify it. It may include "SNAPSHOT", such as "0.21.0-SNAPSHOT" which is what indicates a new version will be available with our next release, but the value should match whatever the template uses.
21+
22+
3. Create Your Engine
23+
24+
Open the [engine](src/engine.ts) file. Set your engine name, which should match the name of `x` above. Follow the rest of the instructions in the file.
25+
26+
4. Create Your Plugin (which acts as a Factory or Provider of one or more engines)
27+
28+
Open the [plugin.ts](src/plugin.ts) file. This currently extends EnginePluginV1, managed from the Engine API. Set your available engine name, which should match the name of `x` above (thought there is the option of adding multiple engines within your plugin as well).
29+
30+
5. Export Your Plugin
31+
32+
Update the [index.ts](src/index.ts) to export your plugin and a `createEnginePlugin` function which returns an instance of your plugin. This will allow for dynamic JIT loading when users decide which engines they want to use to scan their projects.
33+
34+
6. Add Configuration (Optional)
35+
36+
With v5 of Code Analyzer, users are able to add engine-specific [configuration](https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/guide/config-custom.html). If your engine would like to have specific configuration details, add this via a `config.ts` file, and consume it in the Engine constructor.
37+
38+
## About Rules and Violations
39+
40+
When building out the implementation of your rules and violations, we are less prescriptive about how rules need to be stored and written, but encourage flexible solutions that are easy to maintain. Testing is also a must, and note that this monorepo requires 100% testing coverage. We recommend working with [goldfiles](test/test-data/temp-goldfile.json) for reference test data.
41+
42+
## Messaging
43+
44+
Any customer-facing messaging should live in the [`messages.ts`](src/messages.ts) file. Example messages are there to get started.
45+
46+
## Request Build Support
47+
48+
When you are ready for your engine to be available in the Code Analyzer product suite, work with the CA team to publish your engine and update the build process to include the engine in our available engines.
49+
50+
## Release and Documentation
51+
52+
Code Analyzer publishes new versions at the end of every month. Documentation for your engine will need to be updated at the same time, so you will need to work with Code Analyzer Documentation writer to understand the rules that your engine will be provided. Documentation will be available with the other Code Analyzer [Engines](https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/guide/engines.html).
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import eslint from '@eslint/js';
2+
import tseslint from 'typescript-eslint';
3+
4+
export default tseslint.config(
5+
eslint.configs.recommended,
6+
...tseslint.configs.recommended,
7+
{
8+
rules: {
9+
"@typescript-eslint/no-unused-vars": ["error", {
10+
"argsIgnorePattern": "^_",
11+
"varsIgnorePattern": "^_",
12+
"caughtErrorsIgnorePattern": "^_"
13+
}]
14+
}
15+
}
16+
);

packages/ENGINE-TEMPLATE/package.json

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
{
2+
"name": "@salesforce/engine-template",
3+
"description": "^^ Update me: ENGINE-TEMPLATE",
4+
"version": "0.1.0-SNAPSHOT",
5+
"author": "The Salesforce Code Analyzer Team",
6+
"license": "BSD-3-Clause",
7+
"homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview",
8+
"repository": {
9+
"type": "git",
10+
"url": "git+https://github.com/forcedotcom/code-analyzer-core.git",
11+
"directory": "packages/ENGINE-TEMPLATE"
12+
},
13+
"main": "dist/index.js",
14+
"types": "dist/index.d.ts",
15+
"dependencies": {
16+
"@types/node": "^20.0.0",
17+
"@salesforce/code-analyzer-engine-api": "0.21.0-SNAPSHOT"
18+
},
19+
"devDependencies": {
20+
"@eslint/js": "^8.57.1",
21+
"@types/jest": "^29.5.14",
22+
"eslint": "^8.57.1",
23+
"jest": "^29.7.0",
24+
"rimraf": "*",
25+
"ts-jest": "29.2.3",
26+
"typescript": "^5.7.3",
27+
"typescript-eslint": "^8.22.0"
28+
},
29+
"engines": {
30+
"node": ">=20.0.0"
31+
},
32+
"files": [
33+
"dist",
34+
"LICENSE",
35+
"package.json"
36+
],
37+
"scripts": {
38+
"build": "tsc --build tsconfig.build.json --verbose",
39+
"test": "jest --coverage",
40+
"lint": "eslint src/**/*.ts",
41+
"package": "npm pack",
42+
"all": "npm run build && npm run test && npm run lint && npm run package",
43+
"clean": "tsc --build tsconfig.build.json --clean",
44+
"postclean": "rimraf dist && rimraf coverage && rimraf ./*.tgz && rimraf vulnerabilities",
45+
"scrub": "npm run clean && rimraf node_modules",
46+
"showcoverage": "open ./coverage/lcov-report/index.html"
47+
},
48+
"jest": {
49+
"preset": "ts-jest",
50+
"testEnvironment": "node",
51+
"testMatch": [
52+
"**/*.test.ts"
53+
],
54+
"testPathIgnorePatterns": [
55+
"/node_modules/",
56+
"/dist/"
57+
],
58+
"collectCoverageFrom": [
59+
"src/**/*.ts",
60+
"!src/index.ts"
61+
]
62+
}
63+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { DescribeOptions, Engine, EngineRunResults, LogLevel, RuleDescription, RunOptions, Violation } from "@salesforce/code-analyzer-engine-api";
2+
import * as fsp from 'node:fs/promises';
3+
import path from "path";
4+
import { getMessage } from "./messages";
5+
6+
// *** Change the Class Name to match your engine's name
7+
export class TemplateEngine extends Engine {
8+
// *** Change the NAME to match your engine's name
9+
static readonly NAME = "template";
10+
11+
// *** Consider passing in a configuration object from your engine's plugin if you want to provide user-configuration
12+
constructor() {
13+
super();
14+
}
15+
16+
getName(): string {
17+
return TemplateEngine.NAME;
18+
}
19+
20+
// *** Update if you want to get your engine version any other way
21+
public async getEngineVersion(): Promise<string> {
22+
const pathToPackageJson: string = path.join(__dirname, '..', 'package.json');
23+
const packageJson: {version: string} = JSON.parse(await fsp.readFile(pathToPackageJson, 'utf-8'));
24+
return packageJson.version;
25+
}
26+
27+
// *** Remove underscore for private naming convention if you need any of the DescribeOptions
28+
async describeRules(_describeOptions: DescribeOptions): Promise<RuleDescription[]> {
29+
// *** Best Practice - Use RunRulesProgressEvents to keep users informed on the scan progress
30+
this.emitRunRulesProgressEvent(0);
31+
32+
// *** Parse out relevant file types if you engine is language-specific
33+
//const relevantFiles: string[] | undefined;
34+
35+
// *** Get your rules!
36+
const ruleDescriptions: RuleDescription[] = [];
37+
38+
this.emitRunRulesProgressEvent(50);
39+
40+
// *** Retrieval Implementation is up to you, but you'll need to map them into RuleDescription form, such as:
41+
const exampleRule: RuleDescription = {
42+
name: "Example Rule",
43+
severityLevel: 5,
44+
tags: [
45+
"Recommended"
46+
],
47+
description: "The description of your rule",
48+
resourceUrls: []
49+
};
50+
ruleDescriptions.push(exampleRule);
51+
52+
// *** Best Practice - Set RunRulesProgressEvents to 100 before completing
53+
this.emitDescribeRulesProgressEvent(100);
54+
return ruleDescriptions;
55+
}
56+
57+
// *** ruleNames comes from a describeRules call made just before running
58+
async runRules(_ruleNames: string[], _runOptions: RunOptions): Promise<EngineRunResults> {
59+
60+
// *** Best Practice - Use RunRulesProgressEvents to keep users informed on the scan progress
61+
this.emitRunRulesProgressEvent(2);
62+
63+
// *** Get your violations!
64+
const violations: Violation[] = [];
65+
66+
// *** Rule Implementation - map any violations to Violations and CodeLocations
67+
// *** If Violations do not have CodeLocations they will be shown in modal form
68+
69+
// *** Best Practice - Use messages sparingly to keep users informed
70+
// *** Ideal for failures, or an announcement
71+
const one = '1'
72+
this.emitLogEvent(LogLevel.Debug, getMessage('TemplateMessage2', one, '2'));
73+
74+
this.emitRunRulesProgressEvent(100);
75+
return {
76+
violations: violations
77+
};
78+
}
79+
}

packages/ENGINE-TEMPLATE/src/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// This file should only contain the main exports for the package
2+
3+
// *** Change the Plugin Name to match your plugin's name
4+
import { TemplateEnginePlugin } from "./plugin";
5+
import { EnginePlugin } from "@salesforce/code-analyzer-engine-api";
6+
7+
function createEnginePlugin(): EnginePlugin {
8+
return new TemplateEnginePlugin();
9+
}
10+
11+
export { createEnginePlugin, TemplateEnginePlugin }
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {getMessageFromCatalog} from "@salesforce/code-analyzer-engine-api";
2+
3+
const MESSAGE_CATALOG : { [key: string]: string } = {
4+
TemplateMessage1:
5+
`This message has one variable, '%s', in its message.`,
6+
7+
TemplateMessage2:
8+
`This message has two variables, '%s' and %d, in its message.`,
9+
10+
// *** Update this for the engine name
11+
UnsupportedEngineName:
12+
`The TemplateEnginePlugin does not support an engine with name '%s'.`
13+
}
14+
15+
/**
16+
* getMessage - This is the convenience function to get a message out of the message catalog.
17+
* @param msgId - The message identifier
18+
* @param args - The arguments that will fill in the %s and %d markers.
19+
*/
20+
export function getMessage(msgId: string, ...args: (string | number)[]): string {
21+
return getMessageFromCatalog(MESSAGE_CATALOG, msgId, ...args);
22+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import {
2+
ConfigObject,
3+
Engine,
4+
EnginePluginV1,
5+
} from "@salesforce/code-analyzer-engine-api";
6+
import {getMessage} from "./messages";
7+
import { TemplateEngine } from "./engine";
8+
9+
// *** Change the Class Name to match your engine's name
10+
export class TemplateEnginePlugin extends EnginePluginV1 {
11+
12+
// *** Change to match your engine's name
13+
// *** If your plugin provides multiple engines, we accept that here
14+
getAvailableEngineNames(): string[] {
15+
return [TemplateEngine.NAME];
16+
}
17+
18+
async createEngine(engineName: string, _resolvedConfig: ConfigObject): Promise<Engine> {
19+
validateEngineName(engineName);
20+
// *** Update to use _resolvedConfig, if user-configuration is desired
21+
return new TemplateEngine();
22+
}
23+
24+
// *** Methods available for use if user-configuration is desired
25+
//describeEngineConfig(engineName: string): ConfigDescription;
26+
//createEngineConfig(engineName: string, configValueExtractor: ConfigValueExtractor): Promise<ConfigObject>;
27+
28+
}
29+
30+
// *** Best Practice - validate the engine name in every public method
31+
// since this library is public
32+
function validateEngineName(engineName: string) {
33+
if (engineName !== TemplateEngine.NAME) {
34+
throw new Error(getMessage('UnsupportedEngineName', engineName));
35+
}
36+
}

0 commit comments

Comments
 (0)