8. 도구 설정
자동 검사할 항목은 stylelint로 설정하고, 도구가 판단하지 못하는 항목은 리뷰에서 확인합니다.
8.1 stylelint로 자동 검사할 규칙을 설정합니다
자동 검사 범위를 설정으로 고정하고 의미 판단은 리뷰에 남깁니다.
규칙별 담당
stylelint-config-standard를 확장해 아래 규칙을 적용하고 기계가 확인하지 못하는 의미는 리뷰에서 판단합니다.
| Stylelint 규칙 | 담당 컨벤션 |
|---|---|
selector-class-pattern | 규칙 1.2 |
max-nesting-depth | 규칙 4.1 |
keyframes-name-pattern | 규칙 7.2 |
property-disallowed-list, custom-property-pattern | 규칙 5.2. 이름 패턴은 토큰 선언 파일에서만 켭니다 |
selector-attribute-name-disallowed-list | 규칙 4.5 |
selector-max-id | 규칙 1.2. 선택은 클래스로만 합니다 |
selector-pseudo-class-disallowed-list | 규칙 4.7 |
declaration-no-important | 규칙 7.2의 전역 처리만 예외입니다 |
media-feature-range-notation | 규칙 6.2의 범위 표기. 표준 설정에 포함됩니다 |
no-descending-specificity | 자손 기본 블록을 조상 규칙보다 앞에 둡니다. 표준 설정에 포함됩니다 |
selector-disallowed-list는 다음 세 컨벤션을 담당합니다.
no-duplicate-selectors는 규칙 4.4과
규칙 4.3의 단독 재선언을 담당합니다.
폴더별 접두사는 selector-class-pattern과 selector-disallowed-list의 overrides로 나눕니다.
selector-max-combinators와 selector-max-type은 넣지 않습니다.
개수만으로 우리 선택자와 라이브러리 경로를 구분할 수 없습니다.
도구 한계와 리뷰
| 대상 | 도구 한계 | 처리 |
|---|---|---|
| 쉼표 묶음 자체 | disallowInList는 단독 재선언만 검사합니다 | 규칙 4.3로 판단합니다 |
| 최상위 요소 선택자 | ownMarkupPatterns는 &로 시작하는 형태만 검사합니다 | selector-max-type은 쓰지 않고 리뷰에서 확인합니다 |
| 파일별 소유자 하나, 다른 소유자 클래스의 블록 위치 | 같은 레이어의 다른 식별자와 미등록 외부 클래스는 잡지 못합니다 | 리뷰에서 확인합니다 |
중첩 @media와 데스크톱 퍼스트 방향 | at-rule의 최상위 배치를 강제하지 못합니다 | 규칙 6.1으로 판단합니다 |
| 우리 마크업의 구조 선택자 | :first-child, :nth-child()는 클래스에도 붙습니다 | 표기만으로 구분하지 못해 리뷰에서 확인합니다 |
| 역할 이름, 승격 판단, 변형 노출, 포커스 대비 | 기계가 뜻을 판단하지 못합니다 | 리뷰에서 확인합니다 |
공통 규칙에 폴더별 접두사 overrides를 더합니다.
/**
* 우리 클래스에만 문법 강제
* 우리 접두사로 시작하지 않는 클래스는 남의 것이라 검사 제외
*/
const ownClassPattern = (scope) =>
[
"^(?:",
// 우리 접두사로 시작하지 않는 클래스는 통과
`(?!${scope}_).*`,
"|",
// pg_scopeSlug__element 또는 pg_scopeSlug__element--modifier만 통과
`${scope}_[a-z][a-zA-Z0-9]*__[a-z][a-zA-Z0-9]*(?:--[a-z][a-zA-Z0-9]*)?`,
")$",
].join("");
/**
* 우리가 이름을 정하지 않는 라이브러리 클래스
*/
const libraryPrefixes = [/^\.ant-/, /^\.rc-/, /^\.tippy-/, /^\.Mui/];
/**
* 우리가 마크업을 쓰는 자리에서 금지되는 형태
*/
const ownMarkupPatterns = [
// 상태 가상 클래스를 최상위 선택자로 다시 여는 것
/^\.[\w-]+:(hover|focus|focus-visible|focus-within|active|enabled|disabled|checked|visited)/,
// 중첩 안에서 요소 선택자로 우리 마크업을 잡는 것
// 우리가 쓰지 않는 마크업은 stylelint-disable 주석으로 예외 표시
/^&\s*[>+~]?\s*[a-z]/,
];
const disallowed = (foreignScopes) => [
[...foreignScopes, ...libraryPrefixes, ...ownMarkupPatterns],
{splitList: true},
];
export default {
extends: ["stylelint-config-standard"],
rules: {
// 최상위 @media 안의 클래스는 깊이 0. 브레이크포인트 안에서 상태를 한 겹 더 쓸 수 있음
"max-nesting-depth": [1, {ignoreAtRules: ["media", "supports", "container"]}],
// @keyframes 이름은 전역이라 소유자 접두사 필수. 하이픈은 클래스 --수정자 표기와 섞여 금지
"keyframes-name-pattern": "^(pg|wg|ui)_[a-z][a-zA-Z0-9]*__[a-z][a-zA-Z0-9]*$",
// 쉼표 목록에 든 선택자를 아래에서 단독으로 다시 여는 것까지 검출
"no-duplicate-selectors": [true, {disallowInList: true}],
// 움직임 감소 전역 처리 외에는 금지
"declaration-no-important": true,
// 지역 변수 선언 금지. var() 소비는 허용
"property-disallowed-list": ["/^--/"],
// 우리 마크업의 상태는 수정자로 표현
// 라이브러리가 상태를 data-*로 내는 경우가 있어 우리 접두사만 금지
"selector-attribute-name-disallowed-list": [/^aria-/, /^data-(pg|wg|ui)-/],
"selector-max-id": 0,
// 부정 조건은 기본 블록으로 뒤집음. 남의 마크업만 stylelint-disable로 허용
"selector-pseudo-class-disallowed-list": ["not"],
},
overrides: [
{
files: ["src/page/**/*.css"],
rules: {
"selector-class-pattern": ownClassPattern("pg"),
"selector-disallowed-list": disallowed([/^\.(wg|ui)_/]),
},
},
{
files: ["src/component/widget/**/*.css"],
rules: {
"selector-class-pattern": ownClassPattern("wg"),
"selector-disallowed-list": disallowed([/^\.(pg|ui)_/]),
},
},
{
files: ["src/component/ui/**/*.css"],
rules: {
"selector-class-pattern": ownClassPattern("ui"),
"selector-disallowed-list": disallowed([/^\.(pg|wg)_/]),
},
},
{
// 전역 스타일시트는 우리 클래스 문법 대상 아님
files: ["src/style/**/*.css", "src/*.css"],
rules: {
"selector-class-pattern": null,
"keyframes-name-pattern": null,
"property-disallowed-list": null,
// 움직임 감소 전역 처리는 여기서만
"declaration-no-important": null,
},
},
{
// 전역 토큰 파일에만 이름 강제
files: ["src/style/token.css"],
rules: {
"selector-class-pattern": null,
"property-disallowed-list": null,
// var() 사용까지 검사하므로 외부 변수를 소비하는 파일에는 쓰지 않음
"custom-property-pattern": "^app-[a-z0-9-]+$",
},
},
],
};
함께 보기