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 [![npm](https://img.shields.io/npm/v/@cloudalgo/apex-lint)](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) — 引擎 [![npm](https://img.shields.io/npm/v/@cloudalgo/apex-core)](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 插件 [![npm](https://img.shields.io/npm/v/@cloudalgo/eslint-plugin-apex)](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 [![npm](https://img.shields.io/npm/v/@cloudalgo/eslint-parser-apex)](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, 云安全监控, 代码规范, 安全专业人员, 开发工具, 暗色界面, 机器学习, 聊天机器人, 自动化攻击, 静态分析