[ 살펴보기 ] ESLint - Configuration
![[ 살펴보기 ] ESLint - Configuration](https://cdn.hashnode.com/res/hashnode/image/upload/v1726224014004/78bd6613-702f-4a07-b58b-e06445e1b411.jpeg)
ESLint는 우리가 작성하는 코드가 정해 놓은 convention을 지키고 있는지 혹은 피해야 할 anti-pattern을 포함하는지 등 여러가지 rule을 정해놓고 해당 rule을 기반으로 코드를 검사하며 code quality를 유지할 수 있게 도와준다. 해당 포스트는 ESLint 9.9.x version을 기준으로 configuration을 설명한다. 8.x.x version의 ESLint를 사용한다면 configuration 형식이 다르니 주의하자.
ESLint가 실행되어 code를 검사할 때 configuration 파일에 설정된 사항에 따라 검사를 실시한다. configuration 파일에 어떤 설정을 추가할 수 있다 각 설정이 어떤 역할을 하는지 살펴보자.
ESLint configuration 파일이란 project root 경로에 있는 eslint.config.js 파일을 말한다. file extension은 js 말고도 mjs, cjs, ts, mts, cts가 사용될 수 있다.
만약 root 경로에 여러 eslint configuration 파일이 존재한다면 그 중 우선순위가 좋은 file이 적용된다. configuration file extension별 우선순위는 다음과 같다.
eslint.config.jseslint.config.mjseslint.config.cjseslint.config.tseslint.config.mtseslint.config.cts
만약 eslint를 실행하는 directory에 configuration file이 존재하지 않으면 parent directory를 시작으로 user의 home directroy까지configuration file을 찾으며 search 과정에 먼저 발견된 configuration 파일을 적용하여 ESLint를 검사한다.
프로젝트 package.json의 type property가 module로 설정되어 있지 않다면 mjs나 mts extention을 사용해야 한다. ts, mts, cts extention은 추가 설정이 필요하니 해다 부분은 documentation을 참고하자.
이제부터 configuration object에 사용되는 기본 property들을 살펴보자.
files
ESLint 검사 시 configuration에서 설정한 rule을 적용할 파일을 설정한다. 예를들어 files property를 다음과 같이 설정하면 src directory 아래 .js 확장자를 가진 모든 파일에 ESLint 검사를 실시한다.
export default [
{
files: ["src/**/*.js"],
rules: {
"prefer-const": "error",
}
}
];
files의 path는 eslint.config.js 파일의 위치를 기준으로 relative path로 설정하고 minimatch syntax를 따른다.
만약 .gitignore와 같이 .으로 시작하는 file을 검사 대상으로 넣고자 한자면 다음과 같이 설정할 수 있다.
export default [
{
// **/*.gitignore가 아닌 **/.gitignore로 설정해야 한다
files: ["**/.gitignore"],
rules: {
...
}
}
];
ignores
ESLint 검사를 제외할 파일을 설정한다. 만약 다음과 같이 설정하면 .config.js로 끝나는 파일을 제외하고 ESLint 검사가 적용된다.
export default [
{
files: ["src/**/*.js"],
ignores: ["**/*.config.js"],
rules: {
"prefer-const": "error",
}
}
];
ignores 역시 path는 eslint.config.js 파일의 위치를 기준으로 relative path로 설정하고 minimatch syntax를 따른다.
rules
files property에 설정한 파일들에 적용할 ESLint rule을 설정한다. rules에 아무것도 설정하지 않으면 ESLint를 실행해도 아무런 검사를 하지 않는다.
각 rule에 적용된 severity level에 따라 ESLint 검사시 특정 rule에 위반 되었을 때 error를 발생시키거나 warning 메시지만 출력 또는 rule에 위반되어도 별다른 조치 없이 그냥 무시할 수도 있다.
Rule에 적용할 수 있는 severity level은 다음과 같다.
off 또는 0 : 특정 rule에 대한 검사를 하지 않는다.
warning 또는 1 : 특정 rule이 지켜지지 않은 코드가 있을 경우 경고 메시지를 띄운다.
error 또는 2 : 특정 rule이 지켜지지 않은 코드가 있을 경우 에러를 발생시킨다.
예를 들어 prefer-const rule은 let으로 선언된 변수가 선언된 이후로 한번도 값이 새로 할당되지 않았을 때 const가 더 적절하므로 const를 사용하도록 알려주는 rule이다. 만약 let으로 선언되었으나 실제로 값이 새로 할당되지 않아 const가 더 적절한 경우가 있을 경우 ESLint error를 발생시키고 싶으면 다음과 같이 severity를 error로 설정해준다.
export default [
{
files: ["**/index.{js,mjs,cjs,ts}"],
languageOptions: {
globals: {
...globals.browser,
},
},
rules: {
"prefer-const": "error", // error 또는 2
},
},
];
만약 configuration file에서 rule을 설정하는 대신 특정한 rule을 특정 파일에만 적용하고 싶다면 다음과 같이 page 상단에 comments를 이용해 특정 파일에만 ESLint rule을 적용할 수 있다.
/* eslint prefer-const: "error" */
let indexVal = "indexVal";
...
만약 code의 특정 구간만 ESLint를 disable하고 싶다면 다음과 같이 처리할 수 있다.
/* eslint-disable */
let indexVal = "indexVal";
/* eslint-enable */
...
위의 예제는 indexVal 변수 부분에만 ESLint를 disable 시키는 예제다. eslint-enable 이후의 코드는 정상적으로 ESLint가 적용된다.
만약 전체 ESLint를 disable하는게 아닌 특정 rule만 disable해주고 싶다면 다음처럼 rule을 명시해준다.
/* eslint-disable prefer-const */
let indexVal = "indexVal";
/* eslint-enable prefer-const */
위의 같은 방법이 간편할 수 있지만 꼭 필요한 상황일 때만 국한적으로 적용하는 것이 좋다. 위와 같이 ESLint를 inline으로 하나둘씩 disable 시키기 시작하면 결국 ESLint가 제공하는 이점은 줄어들기 마련이다.
또한 rule에 따라 severity level이외 추가로 받을 수 있는 option이 존재할 수 있다. 예를 들어 prefer-const rule에 destructuring option을 all로 설정하여 해당 rule의 destructuring 검사 조건을 변경할 수 있다.
export default [
{
files: ["**/index.{js,mjs,cjs,ts}"],
languageOptions: {
globals: {
...globals.browser,
globalTest: "readonly",
},
},
rules: {
"prefer-const": ["error", { destructuring: "all" }],
},
},
];
설정할 수 있는 option은 rule에 따라 다르므로 option에 대한 정보는 각 rule의 상세정보를 참고하자. prefer-const rule에 대한 option은 다음 documentation에서 찾아볼 수 있다.
plugins
Custom rules을 모아 하나의 plugin을 생성할 수 도 있다. React를 위한 plugin 또는 typescript를 위한 plugin등 community에서 찾아볼 수 있는 plugin이 많으므로 프로젝트에 필요한 plugin을 찾아 적용하면 ESLint 검사시 해당 plugin이 가지고 있는 rule을 포함해서 검사할 수 있다. plugin의 적용방법은 plugin마다 다르므로 적용하고자 하는 plugin의 documentation을 참고하자.
plugin을 적용할 때는 plugin name과 plugin object를 key-value 형태로 선언하여 사용한다. plugin name은 원하는 string값을 설정해도 되지만 일반적으로 npm에 등록된 plugin 이름을 사용한다. 만약 npm에 등록된 이름이 “eslint-plugin-이름” 형식으로 되어 있다면 “eslint-plugin-” 부분을 제외하고 이름 부분만 사용한다. ( Documentation - Tips 부분 참조 )
하지만 위의 사항은 하나의 convention이므로 원한다면 name을 원하는 값으로 설정해도 무방하다. 아래의 예제를 살펴보자.
import testlint from "test-eslint";
export default [
{
files: ["**/*.{js,mjs,cjs,ts}"]
plugins: {
"test-eslint": testlint.plugin,
}
},
];
위의 예제에서 우리는 test-eslint라는 가상의 plugin을 import 해오고 있다. 그리고 testlint.plugin object에 eslint plugin에 적용되어야 할 사항들을 들어있으므로 “test-eslint”라는 plugin name으로 plugin object를 추가한다.
다음은 typescirpt eslint plugin을 적용하는 예제이다.
import tseslint from "typescript-eslint";
import globals from "globals";
export default [
...tseslint.configs.recommended,
{
files: ["**/*.{js,mjs,cjs,ts}"]
languageOptions: {
globals: {
...globals.browser,
},
}
},
];
위의 예제와 같이 plugin recomeded array를 configuration에 추가해주면 해당 plugin 적용에 필요한 files, plugins, parser, rules등이 configuration에 추가되고 ESLint 검사시 해당 plugin이 포함하고 있는 rule도 함께 적용된다. ( typescript-eslint - documentation )
tseslint.configs.recommended를 풀어헤쳐 표현하면 대략 다음과 같다. ( 아래 예제는 실제 recommeded array와 완전히 일치하진 않는다 )
import tseslint from "typescript-eslint";
import globals from "globals";
export default [
{
files: [ '**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts' ],
plugins: { '@typescript-eslint': tseslint.plugin },
languageOptions: { parser: tseslint.parser, sourceType: 'module' },
rules: {
// typescript-eslint recommended에 포함된 rules
}
},
];
실제 적용되는 typescript-eslint plugin의 recommended array의 형태는 대략 위와 같다. plugins property에 typesciprt-eslint plugin이 설정되고 languageOptions.parser에 typesciprt-eslint plugn을 위한 parser 또한 추가하여 default parser를 대체하고 있다. 그리고 rules에는 plugin recommeded array에 포함된 rules이 포함된다.
때로 plugin에서 제공하는 rule의 severity level이나 option을 수정하고 싶을 때가 있을 것이다. 그럴때는 rules property에서 특정 plugin의 rule을 명시하여 수정해준다. 특정 plugin rule을 명시할 때 는 plugin을 추가할 때 설정한 plugin name 그리고 plugin의 rule 이름을 조합하여 plugin name/rule name 형식으로 명시한다.
import testlint from "test-eslint";
export default [
{
files: ["**/*.{js,mjs,cjs,ts}"]
plugins: {
"test-eslint": testlint.plugin,
}
},
{
files: ["**/*.{js,mjs,cjs,ts}"]
rules: {
"test-eslint/no-unused-vars": "warn",
// plugin name / rule name
},
},
];
languageOptions
검사하는 코드가 특정 commonjs syntax에 맞게 작성이 되었는지 등 Javascript language level 관련 설정에 사용할 수 있다. 만약 다음과 같이 설정하면 검사하는 code가commonjs syntax를 준수하는 code인지 검사한다. 즉, 검사하는 code 및 configuration file에서 ECMAScript module ( import, export syntax )를 사용하려고 하면 lint error가 발생한다.
module.exports = [
{
files: ["**/*.{js,mjs,cjs,ts}"],
languageOptions: { sourceType: "commonjs" },
rules: {
"prefer-const": "error",
},
},
];
ESLint가 code를 검사할 때 검사하는 code의 실행환경에서 어떤 global variables가 존재하는지 알지 못하기에 globals property를 통해 알려줘야 한다. 예를들어 다음과 같이 ESLint configuration에 no-undef rule을 추가했다고 가정해보자, 해당 rule은 선언되지 않은 변수를 사용하려 할 때 오류를 발생시킨다.
console.log(window.document);
현재 우리는 client side 코드를 작성하고 있기에 window global object가 존재하는데 ESLint는 window object와 console object도 선언되지 않았다고 lint 에러를 낸다. ESLint 입장에서는 검사하는 code의 환경에서 어떤 global values가 선언되어 있는지 알지 못하기 때문이다. 그럴 때는 아래와 같이 globals property를 통해 ESLint에게 알려줘야 한다.
import globals from "globals";
export default [
{
name: "test/preferTS",
files: ["**/*.{js,mjs,cjs,ts}"],
languageOptions: {
globals: globals.browser,
},
rules: {
"prefer-const": "error",
"no-undef": "error",
},
},
];
위의 예제와 같이 사용된 globals package를 통해 browser에 대한 global value 정보를 ESLint에게 전달할 수 있다. ( NPM - globals )
Plugin 예제에서 살펴보았듯이 특정 plugin을 사용할 때 parser를 함께 사용해야 할 때가 있다. parser를 지정하지않으면 ESLint는 default parser ( Espree )로 abstract syntax tree( AST )를 생성하고 생성된 AST를 기반으로 검사를 수행한다. 만약 project에 typescript와 typescirpt eslint plugin를 사용한다면 아래와 같이 plugin에서 요구되는 parser를 설정해주어야 한다.
import tseslint from "typescript-eslint";
import globals from "globals";
export default [
{
files: [ '**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts' ],
plugins: { '@typescript-eslint': tseslint.plugin },
languageOptions: { parser: tseslint.parser, sourceType: 'module' },
rules: {
// typescript-eslint recommended에 포함된 rules
}
},
];
linterOptions
Linter 관련 option을 설정한다. 만약 noInlineConfig option을 true로 설정하고 lint 검사를 하면 /* eslint eqeqeq: "off", curly: "error" */와 같이 comment를 통해 congifuration을 적용하는 코드를 발견시 warning을 생성한다.
export default [
{
name: "test/preferTS",
files: ["**/*.{js,mjs,cjs,ts}"],
linterOptions: {
noInlineConfig: true,
},
rules: {
"prefer-const": "error",
"no-undef": "error",
},
},
];
Multiple configuration object
만약 여러개의 configuration object가 선언되어 있다면 설정에 따라 object가 서로 merge가 되어 함께 적용될 수도 있고 마지막에 선언된 configuration property가 이전 configuration property의 일부를 override할 수도 있다. 다음 예제를 살펴보자
export default [
// 첫 번째 configuration object
{
files: ["**/*.{js,mjs,cjs,ts}"],
languageOptions: {
globals: {
...globals.browser,
},
},
rules: {
"no-undef": "error",
"prefer-const": "error",
},
},
// 두 번째 configuration object
{
files: ["**/*.{js,mjs,cjs,ts}"],
rules: {
"prefer-const": "warn",
},
},
];
위의 예제에서 실제로 적용되는 각 rule의 severity level은 다음과 같다.
rules: {
"no-undef": "error",
"prefer-const": "warn",
}
하지만 다음과 같이 두 번째 configuration의 files이 첫 번째 configurations의 files과 다르다면 각 files에 적용되는 ESLint 역시 달라진다.
export default [
// 첫 번째 configuration object
{
files: ["**/*.{js,mjs,cjs,ts}"],
languageOptions: {
globals: {
...globals.browser,
},
},
rules: {
"no-undef": "error",
"prefer-const": "error",
},
},
// 두 번째 configuration object
{
files: ["**/about.{js,mjs,cjs,ts}"],
rules: {
"prefer-const": "warn",
},
},
];
만약 configuration이 위와 같을 때 첫 번째 configuration object가 target하는 files에 적용되는 rule은 다음과 같다.
rules: {
"no-undef": "error",
"prefer-const": "error",
},
하지만 두 번째 configuration object가 target하는 file인 about.{js,mjs,cjs,ts}에 적용되는 rule은 다음과 같다.
rules: {
"no-undef": "error",
"prefer-const": "warn",
},
즉, 기본적으로 configuration object들이 merge되지만 conflict가 발생하는 부분은 files property가 일치하는 configuration중 마지막에 선언된 configuration property가 이전 configuration의 property를 override한다.
예를 들어 configuration object를 아래와 같이 하나 더 추가하면
export default [
// 첫 번째 configuration object
{
files: ["**/*.{js,mjs,cjs,ts}"],
languageOptions: {
globals: {
...globals.browser,
},
},
rules: {
"no-undef": "error",
"prefer-const": "error",
},
},
// 두 번째 configuration object
{
files: ["**/about.{js,mjs,cjs,ts}"],
rules: {
"prefer-const": "warn",
},
},
// 세 번째 configuration object
{
files: ["**/order.{js,mjs,cjs,ts}"],
rules: {
"prefer-const": "error",
},
},
];
order.{js,mjs,cjs,ts} 파일에 최종 적용되는 rule은 다음과 같다.
rules: {
"no-undef": "error",
"prefer-const": "off",
},
![[ 살펴보기 ] RDB - Relationships](https://cdn.hashnode.com/res/hashnode/image/upload/v1739711556668/48dc9e84-a621-42aa-9c9f-5fc5c436f0ec.jpeg)
![[ 살펴보기 ] MySQL - Data types](https://cdn.hashnode.com/res/hashnode/image/upload/v1739593589113/530f8704-4d27-42c9-a451-bb5c63150b99.jpeg)
![[ 살펴보기 ] TypeORM - Transactions, Migration](https://cdn.hashnode.com/res/hashnode/image/upload/v1739106042581/980b8133-61d4-406a-a026-65be9c28eace.jpeg)
![[ 살펴보기 ] TypeORM - Relations](https://cdn.hashnode.com/res/hashnode/image/upload/v1738666874402/b688bd0b-b6bb-4f43-87d8-c1b46b59f1b7.jpeg)
![[ 살펴보기 ] TypeORM - Basics](https://cdn.hashnode.com/res/hashnode/image/upload/v1738666803591/bef5df17-7dc7-4123-ae55-004d5042df39.jpeg)