Skip to content

feat: non-standard-naming rule — detect dev-unfriendly state names (#159)#162

Merged
let-sunny merged 4 commits intomainfrom
feat/non-standard-naming-159
Mar 29, 2026
Merged

feat: non-standard-naming rule — detect dev-unfriendly state names (#159)#162
let-sunny merged 4 commits intomainfrom
feat/non-standard-naming-159

Conversation

@let-sunny
Copy link
Copy Markdown
Owner

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

Summary

variant state 이름이 웹+앱 플랫폼 표준과 안 맞으면 감지. missing-interaction-state가 제대로 동작하려면 표준 이름이 필요.

흐름

non-standard-naming (state-name) → "Clicked"는 비표준, "pressed" 사용 권장
  ↓ 디자이너 수정
missing-interaction-state → 이제 pressed로 정확히 매칭

표준 이름 (STANDARD_STATE_NAMES)

default, hover, active, pressed, selected, highlighted, disabled, focus, focused

비표준 → 제안 매핑 (STATE_NAME_SUGGESTIONS)

비표준 제안
On active
Off disabled
Clicked / Tapped pressed
Inactive disabled
Normal / Rest / Enabled default
Hovered hover
Activated active
Checked selected
Unchecked default

변경

  • 17 → 18 룰 (non-standard-naming 추가)
  • node-semantics.tsSTANDARD_STATE_NAMES, STATE_NAME_SUGGESTIONS, STATE_LIKE_PATTERN 공유 상수
  • rule-messages.tsnonStandardNamingMsg.stateName
  • 테스트 7케이스

Related

Test plan

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

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a rule that detects non-standard variant state names and suggests standardized alternatives for consistency.
  • Documentation

    • Updated the rule reference to include the new naming rule with minor severity and suggestion-level impact.
  • Tests

    • Added tests covering detection, suggested replacements, and acceptance of standard/non-state variants.

#159)

Flags variant state names that aren't in the web+mobile platform standard
set (hover, active, pressed, selected, highlighted, disabled, focus, focused).

Suggests the closest standard name (e.g., "Clicked" → "pressed",
"Inactive" → "disabled"). This ensures missing-interaction-state can
properly detect state variants after the designer fixes the names.

Standard names defined in node-semantics.ts (STANDARD_STATE_NAMES) shared
with interaction 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

Warning

Rate limit exceeded

@let-sunny has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 3 minutes and 49 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 3 minutes and 49 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5e440cea-851f-45c7-9dfa-af4ed23ef8fc

📥 Commits

Reviewing files that changed from the base of the PR and between 526fe17 and 8b65d11.

📒 Files selected for processing (1)
  • src/core/rules/node-semantics.ts
📝 Walkthrough

Walkthrough

Adds a new minor rule non-standard-naming that detects non-standard variant state names in COMPONENT_SET nodes and suggests standard equivalents; also adds supporting semantic constants, rule configuration, messages, tests, and docs updates.

Changes

Cohort / File(s) Summary
Rule ID / Contracts
src/core/contracts/rule.ts
Added "non-standard-naming" to the exported RuleId union.
Shared semantics
src/core/rules/node-semantics.ts
Exported STANDARD_STATE_NAMES (Set), STATE_NAME_SUGGESTIONS (mapping), and STATE_LIKE_PATTERN (RegExp) used to recognize and suggest standard state names.
Rule implementation & exports
src/core/rules/naming/index.ts
Introduced non-standard-naming rule definition and nonStandardNamingCheck which scans COMPONENT_SET variant properties, detects state-like non-standard options, and returns findings with suggestions.
Messages & types
src/core/rules/rule-messages.ts
Added NonStandardNamingSubType and nonStandardNamingMsg.stateName() formatter for reporting state-name findings.
Configuration & categorization
src/core/rules/rule-config.ts
Registered "non-standard-naming" under "minor" with severity: "suggestion", score: -1, and enabled: true.
Tests
src/core/rules/naming/non-standard-naming.test.ts, src/core/engine/scoring.test.ts
Added comprehensive tests for the new rule behavior; updated scoring test to include the new rule in the "minor" list.
Documentation
docs/REFERENCE.md
Updated reference table to add non-standard-naming entry under Minor rules with default score -1 and severity suggestion.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

Poem

🐰 I nibble through variants, sniffing each name,
"Clicked" I swap for "pressed"—oh what a game.
From "On" to "active", I gently implore,
Clean names for devs, hopping toward more. ✨

🚥 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: adding a new 'non-standard-naming' rule to detect dev-unfriendly state names, which aligns with the primary objective of the PR.
Linked Issues check ✅ Passed The PR implements all coding requirements from issue #159: adds the non-standard-naming rule [#159] with category 'minor', detects state-like variant options [#159], validates against platform standards [#159], and suggests standard names [#159].
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the non-standard-naming rule. File modifications include: rule definition, configuration, messages, test suite, shared constants, type definitions, and documentation—all scoped to issue #159.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/non-standard-naming-159

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

Research verified against official specs:
- CSS: :hover, :active, :focus, :disabled, :enabled (MDN)
- Material Design 3: pressed, dragged, hovered, focused (m3.material.io)
- UIKit: highlighted, selected, disabled, focused (Apple Developer)

Added "enabled" and "dragged" to STANDARD_STATE_NAMES.
Removed "enabled" from non-standard suggestions (it's a CSS standard).

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 02:46
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: 4

🤖 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/index.ts`:
- Around line 148-155: The user-facing "fix" text in the nonStandardNamingDef
RuleDefinition is out of sync with the rule allowlist; update the fix string on
nonStandardNamingDef so it lists all accepted state names (include enabled,
dragged and the suggested default) alongside the platform-standard names (hover,
active, pressed, selected, highlighted, disabled, focus, focused) so the
remediation text matches the rule behavior.

In `@src/core/rules/naming/non-standard-naming.test.ts`:
- Around line 82-93: The test case in non-standard-naming.test.ts that verifies
platform-standard variant names is missing the newly allowed "Enabled" and
"Dragged" values, so update the makeNode call in the "accepts platform-standard
names (pressed, selected, highlighted, focused)" test to include "Enabled" and
"Dragged" in componentPropertyDefinitions["State"].variantOptions, then run the
nonStandardNaming.check(node, ctx) expectation as-is to ensure the rule accepts
those names too.
- Around line 29-44: The test doesn't exercise "Inactive" because
nonStandardNaming.check stops at the first matching variant option; update the
test so the "Inactive" case is actually matched—either create a separate test
where componentPropertyDefinitions for "State" has variantOptions: ["Inactive"]
(or only ["Inactive"]), or reorder the array to ["Inactive", "On"] so
nonStandardNaming.check will return the disabled suggestion; ensure the test
still calls nonStandardNaming.check(node, ctx) and asserts result!.message
contains "Inactive" and "disabled"/"disabled" suggestion accordingly.

In `@src/core/rules/node-semantics.ts`:
- Around line 134-146: STATE_NAME_SUGGESTIONS currently maps "off" =>
"disabled", which misclassifies an interactive "off" state; update the mapping
in the STATE_NAME_SUGGESTIONS object so that the key "off" maps to "default"
instead of "disabled" (update the string value in the exported constant to
"default" and run any affected tests/linting).
🪄 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: e382081b-d79c-40c1-96c8-de04a593cb1d

📥 Commits

Reviewing files that changed from the base of the PR and between e2f2229 and 63b58bd.

📒 Files selected for processing (8)
  • docs/REFERENCE.md
  • src/core/contracts/rule.ts
  • src/core/engine/scoring.test.ts
  • src/core/rules/naming/index.ts
  • src/core/rules/naming/non-standard-naming.test.ts
  • src/core/rules/node-semantics.ts
  • src/core/rules/rule-config.ts
  • src/core/rules/rule-messages.ts

- off → default (not disabled — off is toggle's base state)
- Fix text updated with full standard name list including enabled/dragged
- Separate test for Inactive → disabled (was hidden behind On)
- Added enabled/dragged acceptance test

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/index.ts`:
- Around line 157-192: The rule nonStandardNamingCheck uses node.name when
building the message (via nonStandardNamingMsg.stateName) but AnalysisNode.name
may be undefined; update nonStandardNamingCheck to guard for missing names by
returning null early if node.name is falsy or by supplying a safe fallback
(e.g., "<unnamed>" or node.id) when calling nonStandardNamingMsg.stateName so
the message construction never receives undefined; ensure the change is applied
where the object with ruleId nonStandardNamingDef.id and subType "state-name" is
created.

In `@src/core/rules/node-semantics.ts`:
- Around line 106-118: There are two consecutive JSDoc blocks above the
STANDARD_STATE_NAMES constant; remove the duplicate by merging them into a
single JSDoc comment immediately preceding STANDARD_STATE_NAMES that contains
the brief description plus the detailed source references (CSS, Material Design
3, UIKit) so only one consolidated documentation block remains above the
STANDARD_STATE_NAMES definition.
🪄 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: 9be2127d-e140-456f-acbd-a8ea79426539

📥 Commits

Reviewing files that changed from the base of the PR and between 63b58bd and 526fe17.

📒 Files selected for processing (3)
  • src/core/rules/naming/index.ts
  • src/core/rules/naming/non-standard-naming.test.ts
  • src/core/rules/node-semantics.ts

Comment on lines +157 to +192
const nonStandardNamingCheck: RuleCheckFn = (node, context) => {
// Only check COMPONENT_SET (variant container)
if (node.type !== "COMPONENT_SET") return null;
if (!node.componentPropertyDefinitions) return null;

for (const prop of Object.values(node.componentPropertyDefinitions)) {
const p = prop as Record<string, unknown>;
if (p["type"] !== "VARIANT") continue;
const options = p["variantOptions"];
if (!Array.isArray(options)) continue;

for (const opt of options) {
if (typeof opt !== "string") continue;
const lower = opt.toLowerCase().trim();

// Skip if it's a standard name
if (STANDARD_STATE_NAMES.has(lower)) continue;

// Check if it matches a known non-standard state name
if (STATE_LIKE_PATTERN.test(opt)) {
const suggestion = STATE_NAME_SUGGESTIONS[lower];
if (suggestion) {
return {
ruleId: nonStandardNamingDef.id,
subType: "state-name" as const,
nodeId: node.id,
nodePath: context.path.join(" > "),
message: nonStandardNamingMsg.stateName(node.name, opt, suggestion),
};
}
}
}
}

return null;
};
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Mar 29, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard against undefined node.name in message construction.

node.name is used at line 184 without a guard. While COMPONENT_SET nodes typically have names, the AnalysisNode type allows name to be undefined. Add an early return or provide a fallback.

Suggested fix
 const nonStandardNamingCheck: RuleCheckFn = (node, context) => {
   // Only check COMPONENT_SET (variant container)
   if (node.type !== "COMPONENT_SET") return null;
+  if (!node.name) return null;
   if (!node.componentPropertyDefinitions) return null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/rules/naming/index.ts` around lines 157 - 192, The rule
nonStandardNamingCheck uses node.name when building the message (via
nonStandardNamingMsg.stateName) but AnalysisNode.name may be undefined; update
nonStandardNamingCheck to guard for missing names by returning null early if
node.name is falsy or by supplying a safe fallback (e.g., "<unnamed>" or
node.id) when calling nonStandardNamingMsg.stateName so the message construction
never receives undefined; ensure the change is applied where the object with
ruleId nonStandardNamingDef.id and subType "state-name" is created.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Keeping as-is. AnalysisNode.name is z.string() (required) in the Zod schema at src/core/contracts/figma-node.ts:75. It cannot be undefined at runtime.

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.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify AnalysisNode.name field definition in the Zod schema
rg -n "name" src/core/contracts/figma-node.ts -C2

Length of output: 554


@let-sunny, confirmed — Line 75 of src/core/contracts/figma-node.ts shows name: z.string() with no .optional() or .nullable(), so node.name is always a defined string by the time any rule check function is invoked. My comment was a false positive. Thanks for the clarification!


✏️ Learnings added
Learnt from: let-sunny
Repo: let-sunny/canicode PR: 162
File: src/core/rules/naming/index.ts:157-192
Timestamp: 2026-03-29T03:02:35.383Z
Learning: In let-sunny/canicode, `AnalysisNode.name` is declared as `name: z.string()` (required, non-optional) at line 75 of `src/core/contracts/figma-node.ts`. It is guaranteed to be a defined string at runtime for all nodes reaching rule check functions. Do not flag `node.name` usage in rule files as potentially undefined.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: let-sunny
Repo: let-sunny/canicode PR: 158
File: src/core/rules/interaction/index.ts:64-75
Timestamp: 2026-03-29T01:43:45.901Z
Learning: In let-sunny/canicode, `hasStateInVariantProps()` in `src/core/rules/interaction/index.ts` intentionally scans ALL `VARIANT`-typed `componentPropertyDefinitions` (not just ones keyed as `"State"`) because Figma variant axis names are freeform — designers use `State`, `Status`, `Mode`, `Type`, or localized names. If any variant option value matches the state regex pattern, the state is considered present. Do not flag this as a false-positive risk.

Learnt from: let-sunny
Repo: let-sunny/canicode PR: 161
File: src/core/rules/node-semantics.ts:157-160
Timestamp: 2026-03-29T02:11:22.940Z
Learning: In let-sunny/canicode, `isExcludedName()` from `src/core/rules/node-semantics.ts` is intentionally NOT used in interaction rules. Interaction detection uses `getInteractiveType()` (via `INTERACTIVE_PATTERNS`) as an independent classification path. `isExcludedName` only affects: naming rules (`src/core/rules/naming/index.ts`), auto-layout exemptions (`src/core/rules/rule-exceptions.ts`), and orchestrator-level node filtering (`src/agents/orchestrator.ts`). Do not flag `cta`, `filter`, or other terms in `EXCLUDED_NAME_PATTERN` as suppressing interactive control analysis.

Learnt from: let-sunny
Repo: let-sunny/canicode PR: 161
File: src/core/rules/node-semantics.ts:102-104
Timestamp: 2026-03-29T02:24:54.672Z
Learning: In let-sunny/canicode (`src/core/rules/node-semantics.ts`), `isInteractiveNode()` is intentionally narrow: it returns `true` only for state-variant interactive elements (button, link, tab, input, toggle) detected via `getInteractiveType()`. Overlay and carousel nodes are a separate semantic category called "behavior triggers" — they require prototype interactions but do NOT have hover/focus/disabled state variants. Do not suggest broadening `isInteractiveNode` to include overlay/carousel; the three categories (state-variant interactive, overlay, carousel) are siblings, not a hierarchy.

Learnt from: CR
Repo: let-sunny/canicode PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-28T00:25:18.075Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Constants must use SCREAMING_SNAKE_CASE naming convention

Learnt from: let-sunny
Repo: let-sunny/canicode PR: 154
File: src/core/rules/token/index.ts:36-135
Timestamp: 2026-03-29T00:24:15.976Z
Learning: In let-sunny/canicode rule implementations (e.g., RuleCheckFn in src/core/rules/token/index.ts and other rule files), follow the engine contract: `RuleCheckFn` must return `RuleViolation | null`, meaning the engine supports only a single violation per node per rule. If `rawValueCheck` (or similar logic) returns as soon as it finds the first matching subtype, treat that as intentional and do not change it to accumulate multiple violations unless the engine contract is updated (tracked in issue `#155`).

Learnt from: let-sunny
Repo: let-sunny/canicode PR: 154
File: src/core/rules/structure/index.ts:296-304
Timestamp: 2026-03-29T00:24:13.455Z
Learning: In let-sunny/canicode, the `non-layout-container` rule in `src/core/rules/structure/index.ts` 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. This predicate is considered sufficient and intentional; do not flag it as too broad.

Learnt from: CR
Repo: let-sunny/canicode PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-28T00:25:18.075Z
Learning: Applies to src/**/rules/rule-config.ts : `no-auto-layout` is the single highest-impact rule with score -10 — empirically validated via ablation experiments

Learnt from: CR
Repo: let-sunny/canicode PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-28T00:25:18.075Z
Learning: Applies to **/*.{ts,tsx} : Types and Interfaces must use PascalCase naming convention

Learnt from: CR
Repo: let-sunny/canicode PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-28T00:25:18.075Z
Learning: Applies to src/cli/**/*.ts : If no `node-id` is provided to CLI, print a warning

Learnt from: let-sunny
Repo: let-sunny/canicode PR: 93
File: src/core/rules/rule-config.ts:76-80
Timestamp: 2026-03-26T01:28:57.785Z
Learning: In the let-sunny/canicode repo, `src/core/rules/rule-config.ts` is automatically adjusted by a nightly calibration pipeline. Do NOT suggest adding inline comments to this file for calibration rationale — the change evidence is tracked in PR descriptions, commit messages, and `data/calibration-evidence.json` instead. Inline comments would create clutter as the file is frequently auto-modified.

Learnt from: CR
Repo: let-sunny/canicode PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-28T00:25:18.075Z
Learning: Applies to src/**/rules/**/*.ts : All rule scores, severity, and thresholds are managed in `rules/rule-config.ts` — rule logic and score config must be intentionally separated

Learnt from: CR
Repo: let-sunny/canicode PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-28T00:25:18.075Z
Learning: Applies to src/**/rules/rule-config.ts : Rule scores can be tuned in `rule-config.ts` without touching rule logic

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@let-sunny let-sunny merged commit 40771ed into main Mar 29, 2026
3 checks passed
@let-sunny let-sunny deleted the feat/non-standard-naming-159 branch March 29, 2026 03:03
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: non-standard-naming rule — detect dev-unfriendly naming conventions

1 participant