Skip to content

fix(eslint-plugin): guard runtime style spreads with type information - #809

Open
owjs3901 wants to merge 1 commit into
mainfrom
fix/type-aware-unknown-style-spreads
Open

owjs3901 wants to merge 1 commit into
mainfrom
fix/type-aware-unknown-style-spreads

Conversation

@owjs3901

@owjs3901 owjs3901 commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Refs #684, #695 (#756 JSX-02 타입 가드 후속, main 24a595763 기반의 독립 PR)

선행 PR 스택 의존성은 없습니다. #763의 기존 수정 파일, 기존 recommended 설정과 스냅샷, 추출기·바인딩·번들러·React 구현 및 WASM 크기 예산은 변경하지 않았습니다.

요약

  • 실제 TypeScript parser services를 사용하는 선택형 no-unknown-style-spread 규칙과 configs.recommendedTypeChecked를 추가했습니다. 런타임 입력에서 온 것으로 증명되는 spread의 타입이 Devup 스타일 키를 가질 수 있을 때 진단하며, 원래 React DOM 속성만 전달하는 타입은 허용합니다.
  • 실제 공개 prop 타입과 TypeChecker로 유한 키, at-rule·테마의 무한 템플릿 키, 교집합의 비발현 값, 제네릭 제약, 인덱스 및 제외 도메인을 판정합니다. 속성 목록 복사, 추가 evaluator, 의존성, 버전 고정 또는 스타일링 런타임은 없습니다.
  • 별도로 main에서 RED 확인한 TS6 shim의 subpath 해석 버그를 수정했습니다. 확장자 없는 typescript/lib/tsserverlibrary 요청의 URL을 잘못 만들어 project service가 ENOENT로 중단되던 문제입니다.

동작

기존 recommended는 타입 검사 없이 종전대로 동작합니다. 다음 설정을 선택해야 새 규칙이 실행됩니다.

import devupUi from '@devup-ui/eslint-plugin'

export default [
  ...devupUi.configs.recommendedTypeChecked,
  {
    languageOptions: {
      parserOptions: {
        projectService: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
  },
]

새 네임스페이스는 @devup-ui/type-checked/no-unknown-style-spread입니다. 기존 플러그인을 재정의하지 않아 두 preset 및 반복한 typed preset이 합성됩니다. 기존 parserOptions.project도 지원합니다.

입력 결과
런타임 DevupProps, 스타일 키를 갖는 named/union/intersection 타입 진단
any, unknown, {}, object, 열린 제네릭 및 이를 포함한 교집합 진단
원래 DOM 속성 및 DOM/nonstyle 제약의 제네릭 허용, DOM color·width 포함
구체적인 HTMLAttributes & object / HTMLAttributes & {} 허용
교집합에서 undefined/null/never만 남은 스타일 키·인덱스 제거된 능력은 진단 근거에서 제외
사용자 선언·augmentation의 스타일 키, React.PointerEvent.width 원래 intrinsic attribute 선언이 아니므로 스타일 가능 타입으로 판정
리터럴·알려진 상수·고정 키의 동적 값 빌드가 처리하며 새 진단 없음

대상은 실제 import binding으로 확인한 9개 Devup 기본 컴포넌트입니다. named alias와 namespace/default 접근을 지원하며 shadowed/type-only/unrelated binding은 제외합니다. 매개변수·catch·arguments·this·재대입 없는 별칭/멤버 및 런타임 spread를 포함한 객체를 제한적으로 추적합니다. 사용자 코드는 실행하지 않습니다.

실제 main WASM의 두 CSS 모드에서 확인한 빌드/lint 합의를 영구 회귀 30개로 유지합니다. 런타임 기본값과 런타임 우선 rest || fallback / rest ?? fallback은 fallback의 스타일까지 전달만 하므로 진단합니다. 알려진 import가 먼저인 표현식은 실제 합성되어 허용합니다. 두 혼합 conditional 방향은 기존 located build error로 거절되므로 새 규칙 진단을 추가하지 않습니다. 추출기와 className·style 병합 구현은 그대로입니다.

실제 main 출력 예:

const { styles = knownImport } = props;
return <div {...styles} className={styles?.className || ""} style={styles?.style} />;

knownImport가 { bg: 'red' }여도 이 경우 background:red가 생성되지 않습니다. 알려진 import만 spread한 control은 실제 CSS를 생성합니다. 이 차이를 타입 진단으로 알리며 런타임 키 분류나 기본 스타일을 삽입하지 않습니다.

DOM 출처는 실제 React namespace의 AST/symbol 및 원래 intrinsic attribute 선언 identity로 증명합니다. ReactElement augmentation이 사용자 스타일 키를 DOM으로 승격하지 못하고 native intrinsic 재선언도 원래 DOM 속성을 지우지 않습니다. TS6 shim은 모든 typescript/subpath 요청을 공식 호환 패키지의 실제 Node resolution에 위임하며, 실제 CJS/ESM·확장자 유무·package.json 및 공식 await-thenable project-service 회귀를 유지합니다.

새로 생기는 오류

  • 새 build error 종류는 없습니다. 기존 혼합 conditional의 located 거절과 추출기 출력은 바꾸지 않았습니다.
  • typed preset을 선택한 스타일 가능 런타임 spread에는 실제 spread 노드의 파일·행·열로 ESLint 오류가 생깁니다: This runtime spread can carry Devup style props. Constrain the type or pass style props explicitly; use build-time-known composition or forward DOM-only props separately.
  • 타입 정보 없이 새 규칙을 켜면 표준 typescript-eslint 설정 오류가 납니다. 공개 Devup/React prop 타입을 해석하지 못하면 파일을 포함한 StyleSchemaError로 실패합니다.
  • TS6 shim 수정은 기존 ENOENT를 제거하며 무관한 module 요청의 resolver는 바꾸지 않습니다.

남는 한계

임의의 component alias·styled 생성 컴포넌트 추론, standalone 호출/import 평가와 분류되지 않은 값 흐름은 빌드 권한에 남깁니다. 스타일은 명시적인 prop 또는 빌드가 아는 합성으로 작성하고 전달 속성은 실제 DOM/nonstyle 타입으로 제한해야 합니다.

무한 문자열 템플릿은 정확한 교집합·차집합 matcher를 사용합니다. 숫자 placeholder 등 matcher가 지원하지 않는 좁은 질의는 실제 도메인에서 고정 후보 '', screen, (display: grid), 0, 1을 만들며, checker가 양쪽 양의 도메인 소속과 모든 제외 도메인 밖임을 증명한 키만 증인으로 인정합니다. 하나의 증인이 있으면 보고하고 전체 도메인 마스크는 보고하지 않습니다. 숫자 템플릿 문법·대수·인터프리터를 구현한 것은 아니므로 matcher와 검증된 증인 모두 전달 가능성을 증명하지 못한 해당 질의만 미보고할 수 있습니다. 다른 유한 키·인덱스 검사는 계속합니다. 이 한정 정책은 coordinator가 승인했습니다.

검증

  • main의 실제 bg 타입에도 진단이 0개이던 RED와 TS6 project-service ENOENT RED를 먼저 보존했습니다. DOM 출처, 비발현 교집합/인덱스, 열린 제네릭, 제외 도메인, numeric witness, UTF-16 및 augmentation/native 재선언도 실제 checker RED 뒤에 수정했습니다. union 의심 건은 재현되지 않아 union 버그 수정으로 주장하지 않습니다.
  • 기존 검토 트리 04b21203eb53406f1eef03b3a9547fdd6cae4bbc: Oracle APPROVE/HIGH, stable Rust 1.99.1 기본 스택 fmt/strict clippy, Rust 9,428/0/0 ignored, WASM/전체 패키지 build/declarations/verify:dist/lint 통과, 공개 API QA 20개 통과.
  • 기존 최신 Bun 1.4.4 gate: 루트 6,529/0, Vite 868/0, 양쪽 실제 All files 100.00% functions / 100.00% lines. LSP는 미설치/기존 거절 상태라 clean LSP 주장은 하지 않습니다.
  • 정상 훅 1차는 769.370초 뒤 새 TS6 cold project-service 테스트의 기본 5초 제한에서 실패했습니다. assertions와 60초 자식 제한을 유지하며 ESNext/types-empty fixture 및 65초 외부 제한을 승인받아 해당 lane 6,529/0/100/100으로 재검증했습니다.
  • 정상 훅 2차는 829.643초 뒤 다른 새 cold typed-config 테스트가 5,049.05ms에 기본 5초 제한으로 실패했습니다. TS6 회귀는 통과했으나 전체 훅은 실패였으며, 두 시도 모두 commit은 생성되지 않았습니다. Windows LLVM 97.71%는 Linux 100% 증거가 아닙니다.
  • W82b에서 cold TypeScript program/service를 시작하는 새 테스트 7개 진입점을 모두 감사했습니다. 실제 React/DOM 선언과 모든 assertion을 유지하면서 공유 fixture를 ESNext+DOM/types-empty/단일 root로 제한하고, React가 필요 없는 key-domain fixture만 ES5/types-empty로 분리했습니다. 타입 검사 callback에 30초(key-domain 15초)를 개별 적용했고 RuleTester의 전역 setter는 변경하지 않았습니다.
  • tarpaulin 직후 새 Bun process의 typed-config 첫 callback은 JUnit 기준 793.839ms였습니다. 전체 18개 JUnit fresh-process 표본의 각 family 최대값은 typed-config 934.430ms, TS6 728.580ms, build agreement 603.355ms, schema 526.117ms, key-domain 143.056ms, main RuleTester 417.025ms, regression RuleTester 620.378ms입니다. 후속 표본을 tarpaulin 직후 표본이라고 주장하지 않습니다. 측정 중 RAM 5.607 GiB에서 다음 process를 시작하지 않고 중단한 뒤 자원 회복 후 이어갔으며 실패 테스트는 없었습니다.
  • 최종 31개 파일 staged tree 8b748172bd5a5fd8babe901e8042adee007e0e07: scoped lint 통과, 루트 6,529/0, Vite 868/0, 실제 양쪽 100.00% functions / 100.00% lines로 전체 재검증했습니다(434.117초). 이전 검토 트리 대비 승인된 테스트 환경/개별 시간 제한만 변경했고 assertion과 테스트 수는 그대로입니다.
  • 승인된 3차 정상 훅 통과: 978.631초, 12 GiB watchdog 내 peak 6,088,708,096 bytes, heartbeat 실패 없음. 실제 lint/tarpaulin/루트 6,529/0/Vite 868/0 및 양쪽 100.00/100.00 통과 뒤 commit a894ffcca3c96f69f004112b48d9ddea59e43473가 정확한 최종 tree에서 생성되었고 작업 트리는 clean입니다. bypass/amend/force는 없었습니다. 앞선 2회 실패를 성공으로 바꾸어 기록하지 않았습니다.
  • 원격 CI 40412887740 SUCCESS, head a894ffcca3c96f69f004112b48d9ddea59e43473, 재실행 0회: publish / benchmark / landing-next-e2e / react-18 / vinext-rsc-css-e2e / webpack-extends-theme / release-compat / wasm-size 8개 모두 통과했습니다. main 전용 release/deploy-pages 및 site upload skip만 예상된 조건부 제외이며 prerender skip은 아닙니다.
  • 실제 Linux publish 로그: Rust 100.00% (19990/19990), 루트 6,529/0, Vite 867/0, 양쪽 All files 100.00% functions / 100.00% lines, Bun preload race 10/0. Linux Vite 수를 로컬 Windows의 868로 바꾸어 기록하지 않습니다. Landing 두 CSS 모드 모두 71 routes / 0 skipped를 확인했습니다. benchmark 비교도 첫 실행에서 통과했고 WASM size 예외조차 사용하지 않았습니다.
  • 모든 빌드·테스트는 직렬/jobs2/기본 스택/CI unset, 6 GiB watchdog(훅 12 GiB), 시작 시 7 GiB RAM·25 GB 디스크 사전 조건으로 실행합니다. assertion·커버리지·버전·WASM 예산을 완화하지 않습니다. 사용자 지시대로 wasm-size만 실패 허용이며 크기 축소 작업은 없습니다.

Refs #684, #695; follows #756 JSX-02 type guard policy

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@codecov

codecov Bot commented Oct 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
...int-plugin/src/configs/recommended-type-checked.ts 100.00% <100.00%> (ø)
packages/eslint-plugin/src/index.ts 100.00% <100.00%> (ø)
packages/eslint-plugin/src/rules/index.ts 100.00% <100.00%> (ø)
...-unknown-style-spread/__tests__/agreement-cases.ts 100.00% <100.00%> (ø)
...no-unknown-style-spread/__tests__/runtime-cases.ts 100.00% <100.00%> (ø)
...nknown-style-spread/__tests__/typed-rule-tester.ts 100.00% <100.00%> (ø)
...rc/rules/no-unknown-style-spread/dom-properties.ts 100.00% <100.00%> (ø)
...-plugin/src/rules/no-unknown-style-spread/index.ts 100.00% <100.00%> (ø)
...in/src/rules/no-unknown-style-spread/key-domain.ts 100.00% <100.00%> (ø)
...rc/rules/no-unknown-style-spread/runtime-source.ts 100.00% <100.00%> (ø)
... and 1 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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.

1 participant