feat: Validation Rule Analyzer — surface formula constraints as inline YAML comments (v2.10.0)#41
feat: Validation Rule Analyzer — surface formula constraints as inline YAML comments (v2.10.0)#41jdschleicher wants to merge 7 commits intomainfrom
Conversation
… fixtures (v2.10.0) - New ValidationRuleAnalyzerService parses *.validationRule-meta.xml files and extracts typed field constraints from <errorConditionFormula> expressions - Supports 11 formula patterns: REGEX, ISBLANK, ISPICKVAL, numeric min/max, length min/max, date relative (TODAY()), and absolute date (DATE(Y,M,D)) - Unknown/complex multi-field formulas return an 'unknown' constraint so callers can emit a descriptive YAML comment instead of crashing - 9 mock XML fixtures covering every constraint type including inactive rules and a complex AND formula - 30 unit tests; 97.67% statement coverage, 100% function coverage - CHANGELOG updated for v2.10.0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ine (v2.10.0) - IRecipeFakerService: add buildDateRecipeValue method signature and ValidationRuleConstraint params - FakerJSRecipeFakerService: constraint-aware text/numeric/currency/picklist/date handlers; fix floating-point precision for large max values (keep as string); differentiate date vs datetime default from-expr - SnowfakeryRecipeFakerService: same constraint-aware handlers; no-constraint currency uses original pydecimal format; datetime uses end_date=\"now\" - RecipeService: thread fieldConstraints through getRecipeFakeValueByXMLFieldDetail; dispatch date/datetime to buildDateRecipeValue - DirectoryProcessor: read validationRules/ subfolder per object, thread constraintMap into field processing pipeline - All 380 tests pass, zero TypeScript errors Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extracted inline unknown-constraint logic from the default case into a reusable `appendValidationRuleComment` helper, and applied it at every return path in `getRecipeValue`. Added `buildDrivenByValidationRuleComment` to ValidationRuleAnalyzerService to produce a consistent inline comment listing the driving rule names. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d phased reporting Adds vscode.window.withProgress (Notification, cancellable) around the recipe generation flow with three progress phases: reading config, processing metadata, and building relationship trees. Updates createRecipeFilesInSubdirectory to accept progress/token/totalFileCount params for future per-file reporting. Activates the ExtensionCommandService test suite (renamed from placeholder). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove leftover console.log calls from FakerJSRecipeProcessor date utility functions; bump package.json version to match CHANGELOG 2.10.0. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
jdschleicher
left a comment
There was a problem hiding this comment.
Triple Code Review — PR #41: Validation Rule Analyzer (v2.10.0)
Reviewer Verdict Summary
| Reviewer | Role | Verdict |
|---|---|---|
| Reviewer 1 | Senior TypeScript / Architecture Engineer | REQUEST CHANGES |
| Reviewer 2 | Senior Performance Engineer | COMMENT |
| Reviewer 3 | Senior Security Engineer | REQUEST CHANGES |
CRITICAL Findings (Block Merge)
[CRITICAL — Security] Regex pattern injection into new Function() execution
File: src/treecipe/src/RecipeFakerService.ts/FakerJSRecipeFakerService/FakerJSRecipeFakerService.ts line 525
Pipeline: XML regex pattern → YAML recipe → FakerJSRecipeProcessor.getFakerJSExpressionEvaluation() → new Function('faker', 'dateUtils', \return (${preparedCode})`)`
The REGEX_PATTERN regex captures everything between quotes as ([^"]+) and embeds it directly into a JavaScript regex literal in the generated YAML recipe:
return `${this.openingRecipeSyntax} faker.helpers.fromRegExp(/${regexConstraint.value}/) ${this.closingRecipeSyntax}`;A validation rule XML file in the user's Salesforce project (or a malicious shared project) can contain:
<errorConditionFormula>NOT(REGEX(Phone, "^test/) + (()=>{require('child_process').execSync('malicious-cmd')})() + /"))</errorConditionFormula>This generates:
${{ faker.helpers.fromRegExp(/^test/) + (()=>{require('child_process').execSync('malicious-cmd')})() + //) }}
When runFakerByRecipe is executed, FakerJSRecipeProcessor feeds this string into new Function(...) and executes it as live JavaScript in the VS Code extension host process. This is arbitrary code execution triggered by a file in the user's workspace.
Required fix: Sanitize the extracted regex pattern before embedding. At minimum, reject patterns containing /) or escape forward slashes. Better: validate the extracted string is a valid regex using new RegExp(value) in a try/catch before embedding, and escape any / characters as \/.
[CRITICAL — Architecture] date/datetime case newly split from OOTB fallback — existing OOTB date overrides silently bypassed
Files: RecipeService.ts lines 114–118, FakerJSRecipeFakerService.ts (prior OOTB map)
Before this PR, date and datetime fields fell through the switch to getFakeValueIfExpectedSalesforceFieldType(), which applied OOTB object-specific overrides (e.g., CloseDate, ActivityDate, Birthdate each had custom date ranges in the OOTB map). The new explicit case 'date': case 'datetime': intercepts ALL date/datetime fields before the OOTB check, permanently bypassing those overrides. Object-specific date fields like CloseDate will now silently get a generic 2023-01-01 → today range instead of the previously tuned −30 days to +90 days business-relevant range.
Required fix: Either check the OOTB map first before calling buildDateRecipeValue, or apply the fallback ordering so OOTB overrides take precedence.
WARNING Findings (Should Fix Before Merge)
[WARNING — Architecture] YAML block scalar + inline comment = malformed YAML
File: RecipeService.ts appendValidationRuleComment()
buildNumericRecipeValueWithPrecisionAndScale and buildDateRecipeValue return YAML block scalar strings beginning with | followed by a newline and indented content:
|
${{ faker.number.int({min: 100, max: 99999}) }}
appendValidationRuleComment appends the comment with ${value} ${comment}, producing:
|
${{ faker.number.int({min: 100, max: 99999}) }} ### Driven by ValidationRule "Rule_Min"
This is valid as a comment within a YAML block scalar body (YAML ignores #-prefixed tokens inside literal block strings), but means the comment is inside the string value, not a true YAML comment, and will appear literally in the recipe file output. This is intentional per the PR description, but note the comment is embedded inside the block scalar text, not on the YAML key line. Consider documenting this behavior clearly, or moving the comment to the preceding line as a true YAML comment (# Driven by... on its own line before the field key).
[WARNING — Architecture/Parity] Snowfakery buildTextRecipeValueWithLength missing lengthMin handling
File: src/treecipe/src/RecipeFakerService.ts/SnowfakeryRecipeFakerService/SnowfakeryRecipeFakerService.ts line 413
FakerJS implements lengthMin in buildTextRecipeValueWithLength via .padEnd(effectiveMin, 'x'). Snowfakery's equivalent only handles lengthMax and silently ignores lengthMin constraints, producing shorter-than-required strings. This violates the dual-backend parity requirement stated in CLAUDE.md.
Required fix: Add lengthMin handling to Snowfakery's buildTextRecipeValueWithLength (e.g., fake.lexify('?' * effectiveMin) + fake.text(max_nb_chars=${effectiveMax - effectiveMin})).
[WARNING — Logic] toExpr defaults to new Date() in all unconstrained paths
File: FakerJSRecipeFakerService.ts buildDateRecipeValue()
Both the relMaxConstraint branch and the final else default both produce new Date() (today), making the range [2023-01-01, today] for all unconstrained date fields. While this matches the prior OOTB behavior for generic date fields, it means future-dated fields (e.g., contract end dates) will never generate future dates from this new code path. Consider whether the default toExpr should be something like new Date(new Date().setFullYear(new Date().getFullYear() + 1)) for non-constrained fields.
[WARNING — Performance] Sequential per-file validation rule I/O within object processing loop
File: DirectoryProcessor.ts readValidationRuleConstraintsForObject()
Each object directory triggers a separate vscode.workspace.fs.readDirectory() + N×readFile() calls for validation rules, sequenced inside the existing per-object for loop (processAllObjectsAndRelationships). For a project with 50 objects and 10 validation rules each, this adds 550 sequential async I/O calls to the critical path. Consider using Promise.all() for the inner per-file reads within each object, similar to how processFieldsDirectory could batch reads.
[WARNING — Performance] _progress, _token, _totalFileCount parameters accepted but unused
File: DirectoryProcessor.ts createRecipeFilesInSubdirectory() lines 270–273
The three new parameters (_progress, _token, _totalFileCount) are declared but never consumed inside the method body (underscore prefix confirms intentional non-use). The token cancellation signal is also not checked anywhere in the withProgress callback's async work, meaning the user's "Cancel" button has no effect. This is scaffolding debt — either wire cancellation now or note it as a tracked follow-up in a // TODO comment.
[WARNING — Type Safety] any typed intermediate in parseValidationRuleXml
File: ValidationRuleAnalyzerService.ts line 79
let parsed: any;
xml2js.parseString(xmlContent, (error, result) => { parsed = result; });result is typed any and so is parsed. All downstream accesses use optional chaining (rule.active?.[0]) which silently returns undefined on shape mismatch. Define a typed interface for the raw xml2js output (e.g., interface RawValidationRuleXml { ValidationRule?: { active?: string[]; ... } }) to get compile-time safety and avoid silent misparse bugs.
[WARNING — Security] errorMessage and ruleName embedded in YAML comments without sanitization
Files: ValidationRuleAnalyzerService.ts buildUnknownRuleYamlComment() and buildDrivenByValidationRuleComment()
Rule names and error messages from XML are embedded directly into YAML comment strings. A rule name or message containing a newline character could break the YAML line structure and inject content into the recipe at parse time. Sanitize by stripping or replacing newlines and other control characters before embedding in comment strings.
Positive Observations
- The
getConstraintsFromValidationRuleXmlcorrectly short-circuits on inactive rules — this is the right behavior and avoids phantom constraints. - The
groupConstraintsByFieldfallback to fullavailablePicklistChoiceswhen all values are excluded (rather than producing an empty list) is a safe defensive choice. - The
extractFirstFieldReferenceFromFormulaheuristic for unknown formulas is a reasonable best-effort and theUNKNOWN_FIELDsentinel makes the unknown case visible in the YAML output. - Removing the leftover
console.logstatements fromFakerJSRecipeProcessoris a clean housekeeping change. - Test coverage is substantial: 439-line test file for
ValidationRuleAnalyzerService, 7 new tests forExtensionCommandService, and comment-propagation tests inRecipeService. The mock XML fixtures are realistic. - The
withProgressUX upgrade ingenerateRecipeFromConfigurationDetailis a genuine improvement for user feedback during long recipe generation runs.
Overall Verdict: REQUEST CHANGES
Two critical issues must be resolved before merge:
- Regex pattern →
new Functioninjection is an arbitrary code execution risk that activates during normalrunFakerByRecipeusage on any Salesforce project containing validation rules with regex formulas. - Date/datetime OOTB override bypass silently regresses field-specific date behavior for known Salesforce objects (Opportunity CloseDate, Event ActivityDate, etc.).
The Snowfakery lengthMin parity gap and the unused cancellation token should also be addressed in this PR since they are part of the same feature surface.
🤖 Generated with Claude Code
1. Security — validate and escape forward slashes in regex constraints before embedding in faker.helpers.fromRegExp(); invalid patterns now produce a TODO comment instead of injectable code 2. Architecture — date/datetime case in RecipeService now only calls buildDateRecipeValue when constraints are present; unconstrained date fields fall through to getFakeValueIfExpectedSalesforceFieldType preserving pre-PR behavior for OOTB field overrides (CloseDate, etc.) 3. Parity — SnowfakeryRecipeFakerService.buildTextRecipeValueWithLength now handles lengthMin via fake.pystr(min_chars, max_chars), matching FakerJS's padEnd behavior Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
ValidationRuleAnalyzerServiceto parse Salesforce validation rule XML and build a field → constraint mapRecipeServiceso generated YAML fields get### Driven by ValidationRule "..."inline commentsappendValidationRuleCommenthelper across all field type casesgenerateRecipeFromConfigurationDetailinvscode.window.withProgresswith three phased progress messagescreateRecipeFilesInSubdirectoryto acceptprogress/token/totalFileCountfor future per-file reportingExtensionCommandService.test.ts(7 new tests) previously held as placeholderIssue
Closes #40
Test plan
npm run jest-test— 396 tests passing, zero regressionsnpm run compile— zero TypeScript errorsnpm run lint— zero ESLint errorsValidationRuleAnalyzerService.test.tscovers formula parsing, field reference extraction, constraint map buildingRecipeService.test.tscovers validation rule comment appended across all field type return pathsExtensionCommandService.test.tscoverswithProgressoptions, phase order, and param pass-through tocreateRecipeFilesInSubdirectory🤖 Generated with Claude Code