cloudalgo/apex-lint
GitHub: cloudalgo/apex-lint
一款零 JVM 依赖的纯 Node.js Salesforce Apex 静态代码分析工具,内置 41 条规则并兼容 PMD 屏蔽语法。
Stars: 0 | Forks: 0
# apex-lint
针对 Salesforce Apex 的无 JVM 静态分析。无需 Java、apex-jorje 或 Code Analyzer 插件 —— 使用与 PMD 7 相同的 ANTLR 语法(`@apexdevtools/apex-parser`)解析 Apex,针对解析树运行 41 条内置规则,并以美观的文本、JSON 或 SARIF 格式输出检查结果。
## 包
### [@cloudalgo/apex-lint](packages/apex-lint-cli/README.md) — CLI
[](https://www.npmjs.com/package/@cloudalgo/apex-lint)
命令行 linter。全局安装并针对任何 sfdx 项目运行。支持 `pretty`、`json` 和 `sarif` 输出,配置文件自动发现,规则/类别过滤,以及兼容 PMD 的屏蔽。
```
npm install -g @cloudalgo/apex-lint
apex-lint force-app/
```
→ [完整 CLI README](packages/apex-lint-cli/README.md) · [npm](https://www.npmjs.com/package/@cloudalgo/apex-lint)
### [@cloudalgo/apex-core](packages/apex-core/README.md) — 引擎
[](https://www.npmjs.com/package/@cloudalgo/apex-core)
可嵌入的静态分析引擎。暴露了一个 `Linter` 类,你可以从任何 Node ≥ 20 的应用程序(编辑器、CI 机器人、VS Code 扩展、构建 pipeline)中调用它。CLI 和 ESLint 插件均基于此包构建。
```
import { Linter, allRules } from "@cloudalgo/apex-core";
const result = new Linter(allRules).lint({ filePath: "MyClass.cls", source });
```
→ [完整引擎 README](packages/apex-core/README.md) · [npm](https://www.npmjs.com/package/@cloudalgo/apex-core)
### [@cloudalgo/eslint-plugin-apex](packages/eslint-plugin-apex/README.md) — ESLint 插件
[](https://www.npmjs.com/package/@cloudalgo/eslint-plugin-apex)
将所有 41 条 Apex 规则引入标准 ESLint 工具的 ESLint 插件。适用于 ESLint v8(传统 `.eslintrc`)和 v9(flat config)。通过 ESLint 扩展启用内联 `// eslint-disable` 屏蔽和 VS Code 集成。
```
// eslint.config.js (ESLint v9)
import apex from "@cloudalgo/eslint-plugin-apex";
export default [...apex.flatConfigs.recommended];
```
→ [完整插件 README](packages/eslint-plugin-apex/README.md) · [npm](https://www.npmjs.com/package/@cloudalgo/eslint-plugin-apex)
### [@cloudalgo/eslint-parser-apex](packages/eslint-parser-apex/README.md) — ESLint parser
[](https://www.npmjs.com/package/@cloudalgo/eslint-parser-apex)
将 `@apexdevtools/apex-parser` 桥接到兼容 ESTree 的 AST 的自定义 ESLint parser。打包在 `eslint-plugin-apex` 内部 —— 仅当您想为 Apex 编写自己的 ESLint 规则时才需要单独安装。
→ [完整 parser README](packages/eslint-parser-apex/README.md) · [npm](https://www.npmjs.com/package/@cloudalgo/eslint-parser-apex)
## 快速开始
```
# 要求 Node >= 20
npm install -g @cloudalgo/apex-lint
# lint 一个 sfdx 项目
apex-lint path/to/force-app
# 或从 monorepo 运行
pnpm install && pnpm -r build
node packages/apex-lint-cli/dist/cli.js path/to/force-app
```
## CLI 参考
```
apex-lint [options]
Options:
-f, --format pretty | json | sarif (default: pretty)
-o, --output write results to file instead of stdout
--fail-on fail build at this severity+ (default: moderate)
critical | high | moderate | low | info
-c, --config path to config json (default: apexlint.config.json)
--rules comma-separated rule IDs to run (default: all)
--exclude-rules comma-separated rule IDs to exclude
--categories comma-separated categories to run
security | performance | error-prone | design
best-practices | code-style
--metadata-root sfdx project dir for SObject metadata (repeatable)
--list-rules print the rule catalog grouped by category
-h, --help show this help
Exit codes: 0 = clean (below threshold), 1 = violations at/above threshold, 2 = usage error.
```
摘要和进度始终打印到 **stderr**;违规输出到 `--output` 或 stdout。CI 日志即使在将结果捕获到文件时也会显示摘要。
### 常见用法
```
# 仅运行安全规则
apex-lint force-app --categories security
# 运行两个特定规则
apex-lint force-app --rules SoqlInLoop,DmlInLoop
# 排除嘈杂的低价值规则
apex-lint force-app --exclude-rules MethodNamingConventions,ApexAssertionsShouldIncludeMessage
# 用于 GitHub code scanning 的 SARIF
apex-lint force-app --format sarif --output results.sarif
# 用于自定义工具的 JSON
apex-lint force-app --format json --output results.json
# 仅在 high+ 时使 CI 失败
apex-lint force-app --fail-on high
# 列出按类别分组的所有 41 条规则
apex-lint --list-rules
```
## 配置
将 `apexlint.config.json`(或 `.apexlintrc.json`)放在项目根目录中。从当前目录和每个扫描路径自动发现。使用 `--config` 传递显式路径。
```
{
"rules": ["SoqlInLoop", "DmlInLoop", "ApexSOQLInjection"],
"excludeRules": ["MethodNamingConventions", "ApexAssertionsShouldIncludeMessage"],
"categories": ["security", "performance"],
"severityOverrides": {
"EmptyCatchBlock": "critical",
"AvoidGlobalModifier": "info"
},
"excludePaths": [
"**/test/**",
"**/*Test.cls",
"**/legacy/**"
],
"maxViolationsPerFile": 50,
"metadataRoots": ["./force-app/main/default"],
"failOn": "high"
}
```
| 字段 | CLI 等效项 | 描述 |
|-------|---------------|-------------|
| `rules` | `--rules` | 仅运行这些规则 ID — 跳过所有其他规则 |
| `excludeRules` | `--exclude-rules` | 跳过这些规则 ID(与 CLI flag 合并) |
| `categories` | `--categories` | 仅运行这些类别中的规则 |
| `severityOverrides` | — | 覆盖每条规则的严重程度 |
| `excludePaths` | — | 要跳过的文件的 Glob 模式(支持 `*`、`**`、`?`) |
| `maxViolationsPerFile` | — | 限制每个文件的违规数(对于大型遗留代码库很有用) |
| `metadataRoots` | `--metadata-root` | 用于 SObject 元数据的 sfdx 项目根目录 |
| `failOn` | `--fail-on` | 导致非零退出码的最小严重程度 |
**优先级:** CLI flag 覆盖配置文件。`rules`(包含列表)优先于 `excludeRules` 和 `categories`。
有关带有完整注释的入门配置,请参见 [`apexlint.config.example.json`](apexlint.config.example.json)。
## 屏蔽
无需修改配置即可内联屏蔽检查结果 —— 兼容 PMD 屏蔽语法,因此现有的屏蔽可以原样迁移。
```
// Suppress all rules on this line
doSomething(); // NOPMD
// Suppress one specific rule on this line
[SELECT Id FROM Account]; // NOPMD: SoqlInLoop
// Suppress a rule for an entire method or class
@SuppressWarnings('PMD.SoqlInLoop')
public void myMethod() { ... }
// Suppress all rules for a class
@SuppressWarnings('PMD')
public class LegacyHelper { ... }
```
被屏蔽的违规行为将单独计算,并显示在摘要行中(`N suppressed`),从而使屏蔽对审查者保持透明。
## 规则
跨越 6 个类别的 41 条内置规则。有关带有示例和修复的完整参考,请参见[文档/rules.md](docs/rules.md)。
### 安全 (10)
| 规则 | 严重程度 | 描述 |
|------|----------|-------------|
| `ApexSOQLInjection` | **critical** | 用户控制的输入流入 `Database.query()` — 污点跟踪 |
| `ApexOpenRedirect` | high | 用户控制的 URL 流入 `PageReference` |
| `ApexSSRF` | high | 用户控制的 URL 流入 `HttpRequest.setEndpoint()` |
| `ApexXSSFromURLParam` | high | 污点数据流入 `ApexPages.Message()` 或 `addError(…, false)` |
| `ApexXSSFromEscapeFalse` | high | `addError(msg, false)` 使用非字面量消息 — 禁用转义 |
| `ApexBadCrypto` | high | `Crypto.*` 调用中存在弱算法 (MD5, SHA-1, HMAC-SHA1) |
| `ApexSharingViolations` | high | 类执行 SOQL/DML 时没有显式声明共享 |
| `DatabaseQueryWithVariable` | high | `Database.query()` 使用非字面量参数 |
| `UnguardedCrudOperation` ★ | high | 没有进行 CRUD/FLS 检查的 DML |
| `ApexCSRF` | moderate | 构造函数中的 DML 在每次 GET 请求时运行 |
★ 具有类型感知(使用 MetadataProvider — 需要 `--metadata-root`)
### 性能 (6)
| 规则 | 严重程度 | 描述 |
|------|----------|-------------|
| `SoqlInLoop` | high | for/while/do 循环内的 SOQL |
| `DmlInLoop` | high | 循环内的 DML |
| `HttpCalloutInLoop` | high | 循环内的 HTTP callout |
| `SoqlInBatchExecute` | moderate | Batch `execute()` 的 SOQL 未绑定到 `scope` 参数 |
| `AvoidNonRestrictiveQueries` | low | 没有 WHERE 子句的 SOQL |
| `SystemDebugInLoop` | low | 循环内的 `System.debug()` |
### 易错 (6)
| 规则 | 严重程度 | 描述 |
|------|----------|-------------|
| `InaccessibleAuraEnabledGetter` | high | 没有 public/global 访问权限的 `@AuraEnabled` 成员 |
| `TestMethodsMustBeInTestClasses` | high | 非 `@IsTest` 类中的 `@IsTest` 方法 — 永不运行 |
| `FutureMethodChaining` | high | `@future` 调用另一个 `@future` — 运行时异常 |
| `EmptyCatchBlock` | moderate | 空的 catch 块静默吞没异常 |
| `OverrideBothEqualsAndHashcode` | moderate | 没有 `hashCode()` 的 `equals()` 会破坏 Map/Set |
| `AvoidHardcodedId` | moderate | 硬编码的 15/18 字符 Salesforce 记录 ID |
### 设计 (8)
| 规则 | 严重程度 | 描述 |
|------|----------|-------------|
| `TriggerInlineLogic` | moderate | 直接在 trigger 主体中执行 SOQL/DML |
| `CyclomaticComplexity` | moderate | 方法复杂度 > 10 |
| `CognitiveComplexity` | moderate | 加权嵌套复杂度 > 15 |
| `AvoidDeeplyNestedIfStmts` | moderate | 嵌套深度 > 4 |
| `ExcessiveParameterList` | low | 参数超过 5 个的方法 |
| `ExcessivePublicCount` | low | 拥有超过 45 个 public 成员的类 |
| `TooManyFields` | low | 拥有超过 15 个字段的类 |
| `UnusedPrivateMethod` | low | 类中从未调用的 Private 方法 |
### 最佳实践 (10)
| 规则 | 严重程度 | 描述 |
|------|----------|-------------|
| `TestWithoutAsserts` | moderate | 没有断言调用的测试方法 |
| `SeeAllDataTrue` | moderate | `@IsTest(SeeAllData=true)` 使用实时 org 数据 |
| `HardcodedUrl` | moderate | 字符串字面量中的 `http://` 或 `https://` URL |
| `QueueableWithoutFinalizer` | low | 没有 `System.attachFinalizer()` 的 Queueable |
| `AvoidGlobalModifier` | low | `global` 类 — 打包后无法删除 |
| `AvoidFutureAnnotation` | low | `@future` — 对于新的异步代码,首选 Queueable |
| `DebugsShouldUseLoggingLevel` | low | 没有 `LoggingLevel` 参数的 `System.debug()` |
| `ApexAssertionsShouldIncludeMessage` | low | 没有失败消息的测试断言 |
| `ApexUnitTestMethodShouldHaveIsTestAnnotation` | low | 已弃用的 `testMethod` 关键字 |
| `ApexUnitTestClassShouldHaveRunAs` | low | 没有 `System.runAs()` 调用的测试类 |
### 代码风格 (1)
| 规则 | 严重程度 | 描述 |
|------|----------|-------------|
| `MethodNamingConventions` | low | 方法名应为 camelCase |
## 架构
```
apex-core
ast/parser.ts wraps @apexdevtools/apex-parser (the only file that touches it)
ast/walk.ts traversal helpers — isInsideLoop, enclosingMethod, walk, …
engine/types.ts Rule / RuleContext / Violation contracts
engine/engine.ts Linter: one tree-walk dispatches all rules (ESLint model)
engine/suppression.ts buildSuppressions() — // NOPMD and @SuppressWarnings('PMD')
metadata/provider.ts MetadataProvider interface ← the seam
metadata/filesystem-provider.ts reads sfdx objects/ from disk
rules/security.ts taint analysis engine + security rules
rules/performance.ts loop-based governor limit rules
rules/design.ts complexity, dead code, structural rules
rules/style.ts testing, naming, best-practice rules
rules/loops.ts SOQL/DML-in-loop
rules/crud.ts unguarded CRUD operations
rules/async.ts @future / Queueable rules
```
**关键设计决策:**
1. **Parser 已被封装,永远不会被规则导入。** 当 Apex 语法增加新语法时,只有 `ast/parser.ts` 会变动。每条规则都能继续正常工作。
2. **类型感知规则依赖于 `MetadataProvider`,而不是 org。** `UnguardedCrudOperation` 在 CI(文件系统)和嵌入到应用程序(实时 org)中运行方式完全相同。如果没有 provider,它会静默降级,而不是触发误报。
3. **针对安全规则的污点分析。** `ApexSOQLInjection`、`ApexOpenRedirect`、`ApexSSRF` 和 `ApexXSSFromURLParam` 使用 PMD 风格的方法内前向传播 —— 源自 VF 参数、REST 请求体或 cookie 的变量通过赋值链被跟踪到注入 sink。不会因安全的内部变量产生误报。
## 添加规则
```
import type { Rule } from "../engine/types.js";
import { isInsideLoop } from "../ast/walk.js";
export const myRule: Rule = {
id: "MyRule",
category: "performance",
severity: "high",
description: "One-line description shown in --list-rules.",
create(ctx) {
return {
QueryContext: (node) => {
if (isInsideLoop(node)) ctx.report(node, "SOQL inside a loop hits governor limits.");
},
};
},
};
```
在 `rules/index.ts` 中注册。要发现某个结构产生哪种上下文类型,请遍历示例文件并打印 `node.constructor.name` — 有关示例,请参见现有规则。
## 路线图
- **PMD 规则集导入** — 读取 `ruleset.xml`,将规则启用/严重程度映射到 apex-lint 配置。
- **基线文件** — 快照当前违规,仅对*新*违规报错(对于旧 org 而言是关键的采用特性)。
- **并行 + 缓存** — 带有针对大型 monorepos 的每文件哈希缓存的 worker_threads。
- **跨方法污点** — 使用基于类的调用图将污点传播扩展到单个方法之外。
## 许可证
BSD-3-Clause。通过 `@apexdevtools/apex-parser` 捆绑 Apex ANTLR 语法(同样为 BSD-3-Clause)。
标签:Apex, GNU通用公共许可证, LNA, MITM代理, Node.js, Salesforce, SOC Prime, 云安全监控, 代码规范, 安全专业人员, 开发工具, 暗色界面, 机器学习, 聊天机器人, 自动化攻击, 静态分析