Skip to content

fix: rule condition review and updates (#152)#163

Merged
let-sunny merged 4 commits intomainfrom
fix/rule-conditions-152
Mar 29, 2026
Merged

fix: rule condition review and updates (#152)#163
let-sunny merged 4 commits intomainfrom
fix/rule-conditions-152

Conversation

@let-sunny
Copy link
Copy Markdown
Owner

@let-sunny let-sunny commented Mar 29, 2026

Summary

17개 룰 전수 검토 — 조건 수정, 면제 정리, subType 구현, 룰 통합.

Changes

Rule Change
non-layout-container Section: children 조건 제거 (UI tree 안에 Section 자체가 잘못)
fixed-size-in-auto-layout 48px threshold 제거 → isVisualOnlyNode 기반
missing-size-constraint wrap/grid subType 구현, 과도한 면제 제거
missing-component minRepetitions 3→2
detached-instance includes() → word boundary regex, exact case
raw-value opacity 1.0 면제
irregular-spacing gridBase 4→2
default-name + non-semantic-name 통합 (18→17 룰)

Rule Reference

전체 룰 조건/subType/면제 문서: Wiki — Rule Reference

의사결정 기록: Wiki — Decision Log

Test plan

  • pnpm lint — clean
  • pnpm test:run — 550/550 pass

Related

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Opacity detection now ignores fully opaque values (fewer false positives)
    • Component-instance matching improved for more accurate associations
  • Changes

    • Naming checks consolidated so similar name issues are reported together; guidance examples expanded
    • Size-constraint reporting now gives scenario-specific guidance (wrap, grid, or max-width)
    • Exemption logic for size/layout tightened to reduce unintended exemptions
  • Documentation

    • Reference docs and example config updated to reflect naming consolidations

- Section: remove children check (Section in UI tree is always wrong)
- fixed-size-in-auto-layout: remove 48px threshold, use isVisualOnlyNode
- missing-size-constraint: implement wrap/grid subTypes, remove
  over-generous exemptions (200px, sibling FILL, TEXT, grid, wrap)
- missing-component: minRepetitions 3→2
- detached-instance: includes() → word boundary regex, exact case
- raw-value: opacity 1.0 no longer flagged
- irregular-spacing: gridBase 4→2
- Merge default-name into non-semantic-name (18→17 rules)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 29, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f19807ed-1570-4e8a-b05a-3930cba2e039

📥 Commits

Reviewing files that changed from the base of the PR and between f286c9e and 98991f9.

📒 Files selected for processing (1)
  • src/core/rules/naming/non-semantic-name.test.ts

📝 Walkthrough

Walkthrough

The standalone default-name rule was removed and its detection folded into non-semantic-name. Rule IDs, configs, docs, messages, and tests were updated. Size-constraint exemption logic was tightened, missing-size-constraint now emits context-aware subTypes, component-name matching uses word-boundary regex, and raw-value opacity checks were refined.

Changes

Cohort / File(s) Summary
Docs & CLI
docs/REFERENCE.md, src/cli/docs.ts
Removed default-name from docs table and updated CLI example config to reference non-semantic-name.
Type Contracts
src/core/contracts/rule.ts
Removed "default-name" from the exported RuleId union.
Rule Config & Messages
src/core/rules/rule-config.ts, src/core/rules/rule-messages.ts
Removed default-name entries from category/config maps; renamed subtype type to include "shape-name"; removed defaultNameMsg; expanded nonSemanticNameMsg examples; tuned missing-component.minRepetitions (3→2) and irregular-spacing.gridBase (4→2).
Naming Rules Implementation
src/core/rules/naming/index.ts
Folded default-name detection into nonSemanticName.check, emitting non-semantic-name findings (including default/shape cases) and adding explicit return-null when not matched; removed standalone defaultName export.
Naming Tests
src/core/rules/naming/default-name.test.ts, src/core/rules/naming/non-semantic-name.test.ts
Deleted default-name test file; expanded non-semantic-name tests with parameterized cases covering default-name detection, shape-name classification, and overlap/boundary cases.
Engine & Scoring
src/core/engine/rule-engine.test.ts, src/core/engine/scoring.test.ts, src/core/engine/scoring.ts
Replaced default-name references with non-semantic-name in tests and example comment; updated scoring tests/fixtures accordingly.
Structure Rules & Tests
src/core/rules/structure/index.ts, src/core/rules/structure/responsive-fields.test.ts
missing-size-constraint now reports context-aware subTypes (wrap, grid, max-width) based on parent layout and exemption checks; non-layout-container no longer requires Section children; tests updated to assert subType outcomes.
Rule Exceptions & Tests
src/core/rules/rule-exceptions.ts, src/core/rules/rule-exceptions.test.ts
Tightened exemption logic: removed size/sibling/GRID/WRAP/TEXT-based exemptions; isFixedSizeExempt now relies on isVisualOnlyNode only; tests adjusted to reflect stricter behavior.
Component Matching & Tests
src/core/rules/component/index.ts, src/core/rules/component/detached-instance.test.ts, src/core/rules/component/missing-component.test.ts
Changed detached-instance matching to escaped word-boundary regex (case-sensitive); added negative tests for case and boundary; updated missing-component dedup and threshold fixtures.
Token Rule
src/core/rules/token/index.ts
raw-value opacity violation now only flags when opacity is present, unbound, and opacity < 1 (previously any non-undefined unbound opacity).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through strings and regex fine,
folded a default name into a clever line.
SubTypes now chatter, sizes stand tall,
tokens and names answer the call.
A little rabbit nudged: "Cleaner rules for all!" 🥕

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: reviewing and updating rule detection conditions, exemptions, and consolidating the default-name rule into non-semantic-name.
Linked Issues check ✅ Passed The PR successfully implements all primary objectives from issue #152: condition reviews for 17 rules, subType expansion for missing-size-constraint (wrap/grid), exemption adjustments, detached-instance word-boundary matching, and rule consolidation (default-name → non-semantic-name).
Out of Scope Changes check ✅ Passed All changes are directly related to the rule condition review objectives: rule logic refinements, test updates, message/type adjustments, and documentation changes align with issue #152 requirements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/rule-conditions-152

Comment @coderabbitai help to get the list of available commands and usage tips.

FIXED children in wrap/grid already have explicit size — min-width
not needed. Only FILL children need constraints to control responsive
behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@let-sunny let-sunny marked this pull request as ready for review March 29, 2026 03:44
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/core/rules/structure/index.ts (1)

292-299: ⚠️ Potential issue | 🟠 Major

Keep SECTION reporting scoped to non-empty sections.

Flagging every SECTION will turn empty or purely organizational sections into false positives, even though this rule’s rationale only applies when the section is actually being used as a container. Please restore the child-presence guard here.

Suggested fix
-  if (node.type === "SECTION") {
+  if (node.type === "SECTION" && (node.children?.length ?? 0) > 0) {
     return {
       ruleId: nonLayoutContainerDef.id,
       subType: "section" as const,
       nodeId: node.id,
       nodePath: context.path.join(" > "),
       message: nonLayoutContainerMsg.section(node.name),
     };
   }
Based on learnings, the `non-layout-container` rule "intentionally flags non-empty `SECTION` nodes (`children.length > 0`) because Figma `SECTION` is not a layout container by design — using it structurally with children is treated as semantic misuse."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/rules/structure/index.ts` around lines 292 - 299, The SECTION branch
currently flags every SECTION; restrict it to only non-empty sections by
checking node.children (e.g., node.children?.length > 0) before returning the
non-layout-container violation. Update the SECTION handling in the rule (where
nonLayoutContainerDef.id and nonLayoutContainerMsg.section(...) are used) to
guard on child presence so empty/organizational SECTION nodes are not reported.
docs/REFERENCE.md (1)

20-40: ⚠️ Potential issue | 🟡 Minor

Bring the reference snippets in sync with the shipped config.

The rule table now reflects the default-name merge, but the schema and example configs still reference default-name, and the field table still documents gridBase as 4. Those snippets are now misleading to copy verbatim. Switch the examples to non-semantic-name only, drop the duplicate disable in the relaxed example, and update the documented default grid base to 2.

Also applies to: 71-82, 143-163

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/REFERENCE.md` around lines 20 - 40, Update the example JSON and field
docs to match the shipped config: replace any occurrences of the rule name
"default-name" with "non-semantic-name" in the "rules" object and example
configs (remove the duplicate disable entry in the relaxed example), and change
the documented default value for `gridBase` from 4 to 2; ensure the Fields table
and example snippets reference `excludeNodeTypes`, `excludeNodeNames`,
`gridBase`, and `rules` consistently with these edits so the examples are
copy-paste accurate.
src/cli/docs.ts (1)

90-104: ⚠️ Potential issue | 🟡 Minor

Fix the stale gridBase default in the CLI docs.

This guide still says gridBase defaults to 4, but RULE_CONFIGS["irregular-spacing"].options.gridBase is now 2. Please update the prose here, and the sample JSON if it is meant to reflect defaults, so canicode docs config matches runtime behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli/docs.ts` around lines 90 - 104, Update the docs text and example JSON
to reflect the current default gridBase of 2 instead of 4: change the prose line
that currently reads "gridBase: spacing grid unit, default 4" to state default
2, and update the sample JSON value for "gridBase" from 4 to 2 so it matches
RULE_CONFIGS["irregular-spacing"].options.gridBase; ensure any nearby
explanatory text referencing the default is consistent with the
RULE_CONFIGS["irregular-spacing"].options.gridBase setting.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/core/engine/scoring.test.ts`:
- Line 307: The test fixture for the "minor" category in
src/core/engine/scoring.test.ts incorrectly duplicates the rule ID
"non-semantic-name", which inflates density because calculateScores() collapses
diversity by ruleId; update the fixture to use only the three unique minor IDs
(e.g., "non-standard-naming", "non-semantic-name",
"inconsistent-naming-convention") and, if you need the same pressure, increase
the inner iteration count in the test loop that builds occurrences (the code
that generates many minor occurrences for the calculateScores() run) rather than
duplicating a ruleId.

In `@src/core/rules/component/index.ts`:
- Around line 301-303: Static analysis flagged a ReDoS risk but the escape used
when building pattern from component.name is correct and safe; update the code
around the loop that iterates over components (the for (const [, component] of
Object.entries(components)) block) to add a brief inline comment explaining that
component.name is escaped with component.name.replace(/[.*+?^${}()|[\]\\]/g,
"\\$&") to force a literal match, that the RegExp is wrapped in \b...\b so no
quantifiers/alternations exist (hence no catastrophic backtracking), and note
the edge-case that JavaScript \b only recognizes ASCII word characters so
non‑ASCII component names (CJK, Cyrillic) may not behave as expected.
- Around line 298-303: The component name matching currently builds a
case-sensitive RegExp in the loop (pattern variable) while the missing-component
rule uses case-insensitive matching; make the behavior consistent and add tests:
either change the RegExp construction in the loop that uses component.name to
include the 'i' flag (or use RegExp.prototype.test with case-normalized strings)
so it matches missing-component’s case-insensitive approach, or update the
missing-component logic to be case-sensitive instead; then add unit tests
exercising both "Button" vs "button" cases to the rule test suite to document
the chosen behavior and update a brief comment near the pattern creation and the
missing-component rule to state the intended case-sensitivity.

In `@src/core/rules/naming/non-semantic-name.test.ts`:
- Around line 10-27: The test currently only asserts that
nonSemanticName.check(node, makeContext()) returns a result with subType
defined; update the test to parameterize node.type alongside name and assert the
exact subtype returned by getDefaultNameSubType: change the it.each input to
tuples like [nodeType, name, expectedSubType] (use makeNode({ name, type }) and
makeContext()), call nonSemanticName.check(node, makeContext()), then
expect(result).not.toBeNull(), expect(result!.ruleId).toBe("non-semantic-name"),
and expect(result!.subType).toBe(expectedSubType) so the test verifies the
precise default-name -> subtype mapping used by getDefaultNameSubType rather
than only checking presence.

In `@src/core/rules/structure/index.ts`:
- Around line 224-258: The code never emits subType "min-width" for a general
FILL child; restore the general min-width path before the max-width fallback by
adding a check in the FILL-handling branch that, after
isSizeConstraintExempt(node, context) is checked, returns the
missingSizeConstraintDef with subType: "min-width" when
node.layoutSizingHorizontal === "FILL" and node.minWidth === undefined (use
missingSizeConstraintMsg.minWidth(node.name) for the message and include
nodeId/nodePath as in the other returns), placing this check before the existing
"max-width" return so the collapsed-on-small-screens case is reachable.

---

Outside diff comments:
In `@docs/REFERENCE.md`:
- Around line 20-40: Update the example JSON and field docs to match the shipped
config: replace any occurrences of the rule name "default-name" with
"non-semantic-name" in the "rules" object and example configs (remove the
duplicate disable entry in the relaxed example), and change the documented
default value for `gridBase` from 4 to 2; ensure the Fields table and example
snippets reference `excludeNodeTypes`, `excludeNodeNames`, `gridBase`, and
`rules` consistently with these edits so the examples are copy-paste accurate.

In `@src/cli/docs.ts`:
- Around line 90-104: Update the docs text and example JSON to reflect the
current default gridBase of 2 instead of 4: change the prose line that currently
reads "gridBase: spacing grid unit, default 4" to state default 2, and update
the sample JSON value for "gridBase" from 4 to 2 so it matches
RULE_CONFIGS["irregular-spacing"].options.gridBase; ensure any nearby
explanatory text referencing the default is consistent with the
RULE_CONFIGS["irregular-spacing"].options.gridBase setting.

In `@src/core/rules/structure/index.ts`:
- Around line 292-299: The SECTION branch currently flags every SECTION;
restrict it to only non-empty sections by checking node.children (e.g.,
node.children?.length > 0) before returning the non-layout-container violation.
Update the SECTION handling in the rule (where nonLayoutContainerDef.id and
nonLayoutContainerMsg.section(...) are used) to guard on child presence so
empty/organizational SECTION nodes are not reported.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9f5cfcdf-2b20-442f-b6e7-7de97a2efbde

📥 Commits

Reviewing files that changed from the base of the PR and between 40771ed and ffa2d69.

📒 Files selected for processing (18)
  • docs/REFERENCE.md
  • src/cli/docs.ts
  • src/core/contracts/rule.ts
  • src/core/engine/rule-engine.test.ts
  • src/core/engine/scoring.test.ts
  • src/core/engine/scoring.ts
  • src/core/rules/component/index.ts
  • src/core/rules/component/missing-component.test.ts
  • src/core/rules/naming/default-name.test.ts
  • src/core/rules/naming/index.ts
  • src/core/rules/naming/non-semantic-name.test.ts
  • src/core/rules/rule-config.ts
  • src/core/rules/rule-exceptions.test.ts
  • src/core/rules/rule-exceptions.ts
  • src/core/rules/rule-messages.ts
  • src/core/rules/structure/index.ts
  • src/core/rules/structure/responsive-fields.test.ts
  • src/core/rules/token/index.ts
💤 Files with no reviewable changes (2)
  • src/core/rules/naming/default-name.test.ts
  • src/core/contracts/rule.ts

Comment on lines +298 to +303
// Check if there's a component in the file with a matching name (word boundary)
const components = context.file.components;
const nodeName = node.name.toLowerCase();

for (const [, component] of Object.entries(components)) {
if (nodeName.includes(component.name.toLowerCase())) {
const pattern = new RegExp(`\\b${component.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
if (pattern.test(node.name)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Search for test cases or comments documenting case-sensitivity behavior for detached-instance

# Check test file for case-sensitivity tests
rg -n -i 'case|lowercase|uppercase|insensitive' src/core/rules/component/detached-instance.test.ts

# Check for any documentation about the case-sensitivity change
rg -n -C2 'exact case|case.?sensitiv' --type=md

Repository: let-sunny/canicode

Length of output: 44


🏁 Script executed:

# First, check if the test file exists and find related files
fd -t f 'detached-instance' --type f
# Also check the structure of the component rules directory
fd -t f . src/core/rules/component/ --type f

Repository: let-sunny/canicode

Length of output: 308


🏁 Script executed:

# Read the actual rule implementation to understand the case-sensitivity logic
cat -n src/core/rules/component/index.ts | head -320 | tail -30

Repository: let-sunny/canicode

Length of output: 1308


🏁 Script executed:

# Search for any PR context or documentation about case-sensitivity
rg -n 'exact case|case.?sensitiv|toLowerCase' src/core/rules/component/
# Also check rule-config.ts for any configuration
rg -n 'detached' src/core/rules/rule-config.ts -A3 -B3

Repository: let-sunny/canicode

Length of output: 755


🏁 Script executed:

# Read the detached-instance test file to check test coverage
cat -n src/core/rules/component/detached-instance.test.ts

Repository: let-sunny/canicode

Length of output: 2398


🏁 Script executed:

# Check for any documentation or PR comments about case-sensitivity
rg -n 'detached|instance' --type=md -A 2 -B 2

Repository: let-sunny/canicode

Length of output: 2015


🏁 Script executed:

# Compare the two rule implementations side by side for context
cat -n src/core/rules/component/index.ts | sed -n '120,145p'

Repository: let-sunny/canicode

Length of output: 1281


Clarify case-sensitivity approach and add test coverage for this rule.

The implementation uses case-sensitive matching, but this choice is neither documented in tests nor documented in comments. More importantly, the missing-component rule in the same file uses case-insensitive matching (lines 125, 139-141), creating an inconsistency.

The test suite has no coverage for case-sensitivity variations (e.g., frame "button" vs component "Button"). Add a test case demonstrating the case-sensitive behavior to make the design choice explicit, or align both rules on a consistent approach.

🧰 Tools
🪛 ast-grep (0.41.1)

[warning] 301-301: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(\\b${component.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/rules/component/index.ts` around lines 298 - 303, The component name
matching currently builds a case-sensitive RegExp in the loop (pattern variable)
while the missing-component rule uses case-insensitive matching; make the
behavior consistent and add tests: either change the RegExp construction in the
loop that uses component.name to include the 'i' flag (or use
RegExp.prototype.test with case-normalized strings) so it matches
missing-component’s case-insensitive approach, or update the missing-component
logic to be case-sensitive instead; then add unit tests exercising both "Button"
vs "button" cases to the rule test suite to document the chosen behavior and
update a brief comment near the pattern creation and the missing-component rule
to state the intended case-sensitivity.

Comment on lines 301 to +303
for (const [, component] of Object.entries(components)) {
if (nodeName.includes(component.name.toLowerCase())) {
const pattern = new RegExp(`\\b${component.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
if (pattern.test(node.name)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Regex escaping is correct; static analysis ReDoS warning is a false positive here.

The escape pattern correctly covers all standard regex metacharacters (. * + ? ^ $ { } ( ) | [ ] \), and the resulting pattern is purely literal characters within \b...\b — no quantifiers or alternations that could cause catastrophic backtracking. The fixtures with names like "Menu/16" and "Platform=Desktop" will be handled correctly.

One edge case worth noting: \b in JavaScript only recognizes ASCII word characters [a-zA-Z0-9_]. If component names contain non-ASCII characters (e.g., CJK, Cyrillic), word boundary matching may behave unexpectedly. This is likely acceptable given the design tool context, but worth documenting if internationalized component naming is expected.

📝 Optional: Add brief comment explaining the escaping rationale
  for (const [, component] of Object.entries(components)) {
+   // Escape regex metacharacters; resulting pattern is pure literals within word boundaries (no ReDoS risk)
    const pattern = new RegExp(`\\b${component.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
    if (pattern.test(node.name)) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (const [, component] of Object.entries(components)) {
if (nodeName.includes(component.name.toLowerCase())) {
const pattern = new RegExp(`\\b${component.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
if (pattern.test(node.name)) {
for (const [, component] of Object.entries(components)) {
// Escape regex metacharacters; resulting pattern is pure literals within word boundaries (no ReDoS risk)
const pattern = new RegExp(`\\b${component.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
if (pattern.test(node.name)) {
🧰 Tools
🪛 ast-grep (0.41.1)

[warning] 301-301: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(\\b${component.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/rules/component/index.ts` around lines 301 - 303, Static analysis
flagged a ReDoS risk but the escape used when building pattern from
component.name is correct and safe; update the code around the loop that
iterates over components (the for (const [, component] of
Object.entries(components)) block) to add a brief inline comment explaining that
component.name is escaped with component.name.replace(/[.*+?^${}()|[\]\\]/g,
"\\$&") to force a literal match, that the RegExp is wrapped in \b...\b so no
quantifiers/alternations exist (hence no catastrophic backtracking), and note
the edge-case that JavaScript \b only recognizes ASCII word characters so
non‑ASCII component names (CJK, Cyrillic) may not behave as expected.

…ase-sensitivity

- Remove duplicate non-semantic-name in scoring test fixture
- Pin exact subType per node type in non-semantic-name tests
- Add case-sensitive + word boundary tests for detached-instance
- min-width general path: rejected (by design, wrap/grid only)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/core/rules/naming/non-semantic-name.test.ts`:
- Around line 39-46: The test currently only asserts that result!.subType is not
"shape-name", which allows false positives; update the assertion in the it.each
block that builds a FRAME node using makeNode and calls
nonSemanticName.check(node, makeContext()) to require the exact subtype "frame"
(e.g., expect(result!.subType).toBe("frame")) so the test asserts the precise
overlap handling for FRAME nodes rather than any non-"shape-name" subtype.
- Line 20: Remove the unnecessary `as any` casts used when creating test nodes:
update the call to makeNode({ name, type: type }) (remove `as any`) and remove
the `as any` cast where the `"ELLIPSE"` literal is used; both `type` values
already match the AnalysisNodeTypeSchema/enum via the test's `as const`
parametrization so just pass the values directly and ensure the tests still
compile against makeNode and AnalysisNodeTypeSchema without the casts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: bfe6cfb0-b55b-4793-aa26-5843eb230aef

📥 Commits

Reviewing files that changed from the base of the PR and between ffa2d69 and f286c9e.

📒 Files selected for processing (3)
  • src/core/engine/scoring.test.ts
  • src/core/rules/component/detached-instance.test.ts
  • src/core/rules/naming/non-semantic-name.test.ts

{ name: "Component 1", type: "COMPONENT", expectedSubType: "component" },
{ name: "Instance 3", type: "INSTANCE", expectedSubType: "instance" },
] as const)("flags default name $name → subType $expectedSubType", ({ name, type, expectedSubType }) => {
const node = makeNode({ name, type: type as any });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify where `as any` is used and inspect makeNode typing contract.
rg -n "as any" src/core/rules/naming/non-semantic-name.test.ts -C2
rg -n "export (const|function) makeNode|makeNode\\s*\\(" src/core/rules/test-helpers.ts -C5
rg -n "type\\s*:\\s*z\\.enum|name\\s*:\\s*z\\.string|AnalysisNode" src/core/contracts/figma-node.ts -C3

Repository: let-sunny/canicode

Length of output: 3461


🏁 Script executed:

# Find all enum values in AnalysisNodeTypeSchema to confirm ELLIPSE is valid
rg -A 50 "AnalysisNodeTypeSchema = z.enum" src/core/contracts/figma-node.ts | head -60

# Look at the complete test case array to see how type is defined
cat -n src/core/rules/naming/non-semantic-name.test.ts | head -30

Repository: let-sunny/canicode

Length of output: 3176


🏁 Script executed:

# Check if there are any TypeScript errors in the test file when running type checking
# Also look at how makeNode is imported and used to understand type inference
cat -n src/core/rules/naming/non-semantic-name.test.ts | sed -n '1,65p'

# Check the exact import statement
rg -n "import.*makeNode" src/core/rules/naming/non-semantic-name.test.ts

Repository: let-sunny/canicode

Length of output: 3493


Remove as any casts to maintain type safety in tests.

Lines 20 and 59 use unnecessary as any casts that bypass TypeScript's type checking without benefit. The parametrized test on line 19 uses as const, which preserves literal types for all test case values—all matching valid enum members in AnalysisNodeTypeSchema. Similarly, "ELLIPSE" on line 59 is a valid enum literal. Both casts can be safely removed.

Suggested fix
-  ] as const)("flags default name $name → subType $expectedSubType", ({ name, type, expectedSubType }) => {
-    const node = makeNode({ name, type: type as any });
+  ] as const)("flags default name $name → subType $expectedSubType", ({ name, type, expectedSubType }) => {
+    const node = makeNode({ name, type });
@@
-  it("allows shape names on leaf shape primitives", () => {
-    const node = makeNode({ type: "ELLIPSE" as any, name: "polygon" });
+  it("allows shape names on leaf shape primitives", () => {
+    const node = makeNode({ type: "ELLIPSE", name: "polygon" });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/rules/naming/non-semantic-name.test.ts` at line 20, Remove the
unnecessary `as any` casts used when creating test nodes: update the call to
makeNode({ name, type: type }) (remove `as any`) and remove the `as any` cast
where the `"ELLIPSE"` literal is used; both `type` values already match the
AnalysisNodeTypeSchema/enum via the test's `as const` parametrization so just
pass the values directly and ensure the tests still compile against makeNode and
AnalysisNodeTypeSchema without the casts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@let-sunny let-sunny merged commit 158a4c3 into main Mar 29, 2026
2 of 3 checks passed
@let-sunny let-sunny deleted the fix/rule-conditions-152 branch March 29, 2026 04:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: define detailed detection conditions for each rule

1 participant