๋ชจ๋“  TypeScript ํŒ€์— ํ•„์š”ํ•œ 20๊ฐ€์ง€ ์ •์  ๋ถ„์„ ๋„๊ตฌ

TypeScript ์ •์  ๋ถ„์„ ๋„๊ตฌ: ๊ฐœ๋ฐœํŒ€์„ ์œ„ํ•œ ์™„๋ฒฝ ๊ฐ€์ด๋“œ

TypeScript์˜ ํƒ€์ž… ์‹œ์Šคํ…œ์€ ์ฝ”๋“œ ์‹คํ–‰ ์ „์— ํƒ€์ž… ๋ถˆ์ผ์น˜, ์†์„ฑ ๋ˆ„๋ฝ, ์ž˜๋ชป๋œ ํ•จ์ˆ˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜์™€ ๊ฐ™์€ ์ค‘์š”ํ•œ ๋ฒ„๊ทธ ์œ ํ˜•์„ ์žก์•„๋ƒ…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ทธ ์™ธ์˜ ๋ชจ๋“  ๊ฒƒ์„ ์žก์•„๋‚ผ ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‚ด ๋ฐ์ดํ„ฐ ํ๋ฆ„ ๋ฐฉ์‹์— ๋”ฐ๋ผ ๋ฐœ์ƒํ•˜๋Š” ๋ณด์•ˆ ์ทจ์•ฝ์ , ํŒ€ ๊ฐ„ ๊ฒฝ๊ณ„๊ฐ€ ๋ชจํ˜ธํ•ด์ง€๋ฉด์„œ ์‹œ๊ฐ„์ด ์ง€๋‚จ์— ๋”ฐ๋ผ ๋ˆ„์ ๋˜๋Š” ์•„ํ‚คํ…์ฒ˜ ์œ„๋ฐ˜, ํ˜ธ์ถœ์ž๊ฐ€ ์ œ๊ฑฐ๋œ ํ›„์—๋„ ์ฝ”๋“œ๋ฒ ์ด์Šค์— ์˜ค๋žซ๋™์•ˆ ๋‚จ์•„ ์žˆ๋Š” ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ๋‚ด๋ณด๋‚ด๊ธฐ, ์ปดํŒŒ์ผ์€ ์ œ๋Œ€๋กœ ๋˜์ง€๋งŒ ํŠน์ • ๋Ÿฐํƒ€์ž„ ์กฐ๊ฑด์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์˜ค๋ฅ˜ ๋“ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ •์  ๋ถ„์„ ๋„๊ตฌ๋Š” ์ด๋Ÿฌํ•œ ๋ถ€๋ถ„์„ ๋ณด์™„ํ•˜๋ฉฐ, ์–ด๋–ค ๋„๊ตฌ๋ฅผ ์„ ํƒํ• ์ง€๋Š” ์‹ค์ œ๋กœ ๋ฌด์—‡์„ ์ฐพ๊ณ ์ž ํ•˜๋Š”์ง€์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง‘๋‹ˆ๋‹ค.

์ด ๊ฐ€์ด๋“œ์—์„œ๋Š” 2026๋…„ TypeScript ํŒ€์— ์ค‘์š”ํ•œ ๋„๊ตฌ๋“ค์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—๋Š” ๋ฆฐํŒ… ๊ณ„์ธต(ESLint์™€ typescript-eslint, Biome, OxcLint), ๋ณด์•ˆ ๊ณ„์ธต(Semgrep, Snyk Code, SonarQube), ์•„ํ‚คํ…์ฒ˜ ๊ณ„์ธต(Dependency-Cruiser, Deptrac, Nx), ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ์ฝ”๋“œ ๊ณ„์ธต(Knip), ๊ทธ๋ฆฌ๊ณ  ์‹ฌ์ธต ๋ถ„์„ ๊ณ„์ธต(ts-morph, TypeScript ์ปดํŒŒ์ผ๋Ÿฌ ์ž์ฒด)์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. ๊ฐ ๋„๊ตฌ์— ๋Œ€ํ•ด ์‹ค์ œ ๊ธฐ๋Šฅ, ์„ค์ • ๋ฐฉ๋ฒ•, ๊ทธ๋ฆฌ๊ณ  ์–ด๋–ค ๊ฒฝ์šฐ์— ์ ํ•ฉํ•˜์ง€ ์•Š์€์ง€ ์ž์„ธํžˆ ์‚ดํŽด๋ด…๋‹ˆ๋‹ค.

์•„๋ฌด๋„ ์ดํ•ดํ•˜์ง€ ๋ชปํ•˜๋Š” ์ฝ”๋“œ๋ฒ ์ด์Šค๋ฅผ ์œ„ํ•ด ๋งŒ๋“ค์–ด์กŒ์Šต๋‹ˆ๋‹ค.

SMART TS XL ์ฝ”๋“œ๋ฒ ์ด์Šค ์ „์ฒด์— ๊ฑธ์ณ ํ˜ธํ™˜์„ฑ์„ ๊นจ๋œจ๋ฆฌ๋Š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ž๋™์œผ๋กœ ์•Œ๋ ค์ค๋‹ˆ๋‹ค.

๋” ์•Œ์•„๋ณด๊ธฐ

์ฐจ๋ก€

TypeScript ์ •์  ๋ถ„์„ ๋„๊ตฌ: ๋น„๊ตํ‘œ

๊ฐœ๋ณ„ ๋„๊ตฌ๋ฅผ ์ž์„ธํžˆ ์‚ดํŽด๋ณด๊ธฐ ์ „์— ์•„๋ž˜ ํ‘œ์—์„œ ๊ฐ ๋„๊ตฌ์˜ ์ฃผ์š” ๊ธฐ๋Šฅ, CI ์ ํ•ฉ์„ฑ ๋ฐ ์ด์ƒ์ ์ธ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ์ธก๋ฉด์„ ์•„์šฐ๋ฅด๋Š” ๋‹จ์ผ ๋„๊ตฌ๋Š” ์—†์œผ๋ฉฐ, ํšจ๊ณผ์ ์ธ TypeScript ํ’ˆ์งˆ ๋ถ„์„์—๋Š” ์—ฌ๋Ÿฌ ๋„๊ตฌ๋ฅผ ์กฐํ•ฉํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

์ˆ˜๋‹จ์ฃผ์š” ๊ธฐ๋ŠฅCI ์ ํ•ฉ๋น„์šฉ์ง€์› ๊ธฐ๊ธฐ
ESLint + typescript-eslint๋ฆฐํŒ…, ์Šคํƒ€์ผ, ํƒ€์ž… ์ธ์‹ ๊ทœ์น™๊ฐ€๋Šฅ๋ฌด๋ฃŒํŒ€ ์ „์ฒด ๊ทœ์น™, ๋น„๋™๊ธฐ ํƒ€์ž… ์•ˆ์ „์„ฑ
๋ฐ”์ด์˜ค ๋ฉ”๋ฆฐํŒ… + ํฌ๋งทํŒ…๊ฐ€๋Šฅ๋ฌด๋ฃŒESLint + Prettier ๋Œ€์ฒดํ’ˆ, ์†๋„ ํ–ฅ์ƒ
์˜ฅ์Šฌ๋ฆฐํŠธ๋ฆฐํŒ…(ESLint ํ˜ธํ™˜)๊ฐ€๋Šฅ๋ฌด๋ฃŒ๋น ๋ฅธ ๋ฆฐํŠธ ๊ฒ€์‚ฌ ์‹œ๊ฐ„์ด ํ•„์š”ํ•œ ๋ชจ๋…ธ๋ ˆํฌ
ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์ปดํŒŒ์ผ๋Ÿฌ(tsc)์œ ํ˜• ํ™•์ธ๊ฐ€๋Šฅ๋ฌด๋ฃŒํƒ€์ž… ์˜ค๋ฅ˜, ์—„๊ฒฉํ•œ ๋ชจ๋“œ ์ ์šฉ
์…ˆ๊ทธ๋ ™SAST, ์‚ฌ์šฉ์ž ์ •์˜ ํŒจํ„ด๊ฐ€๋Šฅ๋ฌด๋ฃŒ + ์œ ๋ฃŒ๋ณด์•ˆ ๊ฒ€์‚ฌ, ์‚ฌ์šฉ์ž ์ง€์ • ์กฐ์ง ๊ทœ์น™
Snyk ์ฝ”๋“œSAST, ์ข…์†์„ฑ ๋ณด์•ˆ๊ฐ€๋Šฅ๋ฌด๋ฃŒ + ์œ ๋ฃŒ๋ณด์•ˆ ์šฐ์„  ํŒ€, IDE ํ†ตํ•ฉ
์†Œ๋‚˜ํ๋ธŒ / ์†Œ๋‚˜ํด๋ผ์šฐ๋“œํ’ˆ์งˆ ๊ด€๋ฆฌ, ์ถ”์„ธ ์ถ”์ ๊ฐ€๋Šฅ๋ฌด๋ฃŒ + ์œ ๋ฃŒ๊ธฐ์—…์šฉ ๊ณ ํ’ˆ์งˆ ๋Œ€์‹œ๋ณด๋“œ
์†Œ๋‚˜๋ฆฐํŠธIDE ์ˆ˜์ค€์˜ ํ’ˆ์งˆ ํ”ผ๋“œ๋ฐฑIDE ์ „์šฉ๋ฌด๋ฃŒ์ธ๋ผ์ธ ๋ณด์•ˆ ๋ฐ ํ’ˆ์งˆ ๊ด€๋ จ ํŒ
์ข…์†์„ฑ-ํฌ๋ฃจ์ €์˜์กด์„ฑ ๊ทธ๋ž˜ํ”„ ๊ฐ•์ œ ์ ์šฉ๊ฐ€๋Šฅ๋ฌด๋ฃŒ์•„ํ‚คํ…์ฒ˜ ๊ทœ์น™ ๊ฒ€์ฆ
๋ށํŠธ๋ž™๋ ˆ์ด์–ด ๊ฒฝ๊ณ„ ๊ฐ•์ œ ์ ์šฉ๊ฐ€๋Šฅ๋ฌด๋ฃŒํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜, DDD ๊ฒฝ๊ณ„
Nx๋ชจ๋…ธ๋ ˆํฌ ์ข…์†์„ฑ ๊ด€๋ฆฌ๊ฐ€๋Šฅ๋ฌด๋ฃŒ + ์œ ๋ฃŒ๋ชจ๋…ธ๋ ˆํฌ ๋ชจ๋“ˆ ๊ฒฝ๊ณ„
์ž๋ฅด๋‹ค์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ์ฝ”๋“œ ๋ฐ ๋ฏธ์‚ฌ์šฉ ๋‚ด๋ณด๋‚ด๊ธฐ๊ฐ€๋Šฅ๋ฌด๋ฃŒ๋Œ€๊ทœ๋ชจ๋กœ ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ์ฝ”๋“œ ์ค„์ด๊ธฐ
ts-๋ชจํ”„ํ”„๋กœ๊ทธ๋žจ ๋ฐฉ์‹ TS AST ๋ถ„์„์„ ํƒ์ ์ธ๋ฌด๋ฃŒ์‚ฌ์šฉ์ž ์ •์˜ ๋ถ„์„, ์ฝ”๋“œ ์ˆ˜์ •, ๋„๊ตฌ

1๋‹จ๊ณ„: ๋ณดํ’€ ์ œ๊ฑฐ ๋ฐ ์Šคํƒ€์ผ๋ง

typescript-eslint๋ฅผ ์‚ฌ์šฉํ•œ ESLint

ESLint๋Š” ์—ฌ์ „ํžˆ TypeScript ๋ฆฐํŒ…์˜ ๊ธฐ๋ฐ˜์ž…๋‹ˆ๋‹ค. typescript-eslint ์ด ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด TypeScript์˜ ํƒ€์ž… ์ •๋ณด์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ณ  ํƒ€์ž… ์ปจํ…์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•œ ๊ทœ์น™์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ๊ทธ์ค‘ ๊ฐ€์žฅ ์œ ์šฉํ•œ ๊ฒƒ์€ ์ผ๋ฐ˜์ ์ธ Promise ์˜ค๋ฅ˜๋ฅผ ์žก์•„๋‚ด๋Š” ๋น„๋™๊ธฐ ๊ด€๋ จ ๊ทœ์น™์ž…๋‹ˆ๋‹ค.

์„ธ๊ฒŒ ๋•Œ๋ฆฌ๋‹ค

npm install --save-dev typescript-eslint

์ž๋ฐ” ์Šคํฌ๋ฆฝํŠธ

// eslint.config.js
import tseslint from "typescript-eslint";

export default tseslint.config(
  ...tseslint.configs.strictTypeChecked,
  {
    languageOptions: {
      parserOptions: {
        project: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
    rules: {
      // Async safety rules -- highest value TypeScript-specific rules
      "@typescript-eslint/no-floating-promises": "error",
      "@typescript-eslint/await-thenable": "error",
      "@typescript-eslint/no-misused-promises": "error",
      "@typescript-eslint/require-await": "warn",
      // Type safety
      "@typescript-eslint/no-explicit-any": "warn",
      "@typescript-eslint/no-unsafe-assignment": "error",
      "@typescript-eslint/no-unsafe-return": "error",
    },
  }
);

์œ„์˜ ๋„ค ๊ฐ€์ง€ ๋น„๋™๊ธฐ ๊ทœ์น™์€ typescript-eslint๊ฐ€ ์ผ๋ฐ˜ ESLint์— ๋น„ํ•ด ์ œ๊ณตํ•˜๋Š” ๊ฐ€์žฅ ๊ฐ€์น˜ ์žˆ๋Š” ์ถ”๊ฐ€ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ์ด ๊ทœ์น™๋“ค์€ ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ ํ”„๋กœ๋ฏธ์Šค(๋– ๋‹ค๋‹ˆ๋Š” ํ”„๋กœ๋ฏธ์Šค)๋ฅผ ์žก์•„๋ƒ…๋‹ˆ๋‹ค. await Promise๊ฐ€ ์•„๋‹Œ ๊ฐ’์— ์ ์šฉ๋˜๊ฑฐ๋‚˜, ๋™๊ธฐ ํ•จ์ˆ˜๋ฅผ ๊ธฐ๋Œ€ํ•˜๋Š” ์ฝœ๋ฐฑ์— ์ „๋‹ฌ๋˜๋Š” Promise, ๊ทธ๋ฆฌ๊ณ  ๋น„๋™๊ธฐ ํ•จ์ˆ˜๋ฅผ ์ „ํ˜€ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ์— ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. await์ด๋Ÿฌํ•œ ํŒจํ„ด๋“ค์€ ์ปดํŒŒ์ผ์€ ๋ฌธ์ œ์—†์ด ๋˜์ง€๋งŒ ํŠน์ • ์กฐ๊ฑด์—์„œ ๋Ÿฐํƒ€์ž„ ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.

ESLint๊ฐ€ ํ•  ์ˆ˜ ์—†๋Š” ๊ฒƒ : ํŒŒ์ผ ๊ฐ„ ๋ฐ์ดํ„ฐ ํ๋ฆ„ ๋ถ„์„, ์•„ํ‚คํ…์ฒ˜ ๊ฒฝ๊ณ„ ์ ์šฉ, ๋ณด์•ˆ ์˜ค์—ผ ์ถ”์  ๋˜๋Š” ๋น„ํ™œ์„ฑ ๋‚ด๋ณด๋‚ด๊ธฐ ๊ฐ์ง€. ์ด๋Ÿฌํ•œ ๊ธฐ๋Šฅ์„ ์œ„ํ•ด์„œ๋Š” ํ•˜์œ„ ๊ณ„์ธต์˜ ๋„๊ตฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

Biome: ํ˜„๋Œ€์ ์ธ ESLint + ๋” ๋ณด๊ธฐ ์ข‹์€ ๋Œ€์ฒด ํ”„๋กœ๊ทธ๋žจ

Biome์€ ESLint์™€ Prettier๋ฅผ ํ•˜๋‚˜์˜ Rust ๊ธฐ๋ฐ˜ ๋ฐ”์ด๋„ˆ๋ฆฌ๋กœ ๋Œ€์ฒดํ•˜๋ฉฐ, ์‹คํ–‰ ์†๋„๋Š” 25~35๋ฐฐ ๋น ๋ฆ…๋‹ˆ๋‹ค. JavaScript, TypeScript, JSX, JSON์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ESLint ํ”Œ๋Ÿฌ๊ทธ์ธ์˜ ๋ณต์žก์„ฑ๊ณผ ๋Œ€๊ทœ๋ชจ ์ฝ”๋“œ๋ฒ ์ด์Šค์—์„œ์˜ ๋А๋ฆฐ ์„ฑ๋Šฅ์— ๋ถˆ๋งŒ์„ ๋А๋ผ๋Š” ์‹ ๊ทœ ํ”„๋กœ์ ํŠธ๋‚˜ ํŒ€์—๊ฒŒ Biome์€ ๊ฐ€์žฅ ๊ฐ•๋ ฅํ•œ ์ตœ์‹  ๋Œ€์•ˆ์ž…๋‹ˆ๋‹ค.

์„ธ๊ฒŒ ๋•Œ๋ฆฌ๋‹ค

npm install --save-dev --save-exact @biomejs/biome
npx @biomejs/biome init

์„ธ๊ฒŒ ๋•Œ๋ฆฌ๋‹ค

# Check and format in one pass
npx @biomejs/biome check --write src/

# CI mode -- no modifications, exit non-zero on any finding
npx @biomejs/biome ci src/

Biome์˜ ํ”Œ๋Ÿฌ๊ทธ์ธ ์ƒํƒœ๊ณ„๋Š” ESLint๋ณด๋‹ค ๊ทœ๋ชจ๊ฐ€ ์ž‘์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์‚ฌ์šฉ์ž ์ง€์ • ๊ทœ์น™์ด๋‚˜ ํ”„๋ ˆ์ž„์›Œํฌ๋ณ„ ํ”Œ๋Ÿฌ๊ทธ์ธ์ด ๋งŽ์€ ํŒ€์—๊ฒŒ ์ค‘์š”ํ•œ ์š”์†Œ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํ•ต์‹ฌ ESLint ๊ทœ์น™๊ณผ Prettier ํฌ๋งทํŒ…๋งŒ ์‚ฌ์šฉํ•˜๋Š” ํŒ€์˜ ๊ฒฝ์šฐ, Biome์€ ํ›จ์”ฌ ์งง์€ ์‹คํ–‰ ์‹œ๊ฐ„์œผ๋กœ ๋™๋“ฑํ•œ ์ˆ˜์ค€์˜ ์ฝ”๋“œ ๊ฒ€์ฆ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

OxcLint: Speed-First ESLint ํ˜ธํ™˜์„ฑ

OxcLint(Oxc ํ”„๋กœ์ ํŠธ์˜ ์ผ๋ถ€)๋Š” ESLint์™€ ํ˜ธํ™˜๋˜๋Š” ๊ทœ์น™์„ 50~100๋ฐฐ ๋น ๋ฅธ ์†๋„๋กœ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ESLint ์ƒํƒœ๊ณ„ ์ „์ฒด๋ฅผ ๋Œ€์ฒดํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ์ง€๋งŒ, ๋ฆฐํŠธ ์‹œ๊ฐ„์ด ๋ณ‘๋ชฉ ํ˜„์ƒ์ธ CI ํŒŒ์ดํ”„๋ผ์ธ์—์„œ ๊ฐ€์žฅ ๋น ๋ฅธ ์˜ต์…˜์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์„ธ๊ฒŒ ๋•Œ๋ฆฌ๋‹ค

npm install --save-dev oxlint
npx oxlint src/

OxcLint๋Š” ํ‘œ์ค€ ESLint ์‹คํ–‰์— ๋ช‡ ๋ถ„์ด ์†Œ์š”๋˜๊ณ  ํ”ผ๋“œ๋ฐฑ ๋ฃจํ”„ ์ง€์—ฐ์œผ๋กœ ์ธํ•ด ํ’€ ๋ฆฌํ€˜์ŠคํŠธ ํ’ˆ์งˆ์ด ์ €ํ•˜๋˜๋Š” ๋Œ€๊ทœ๋ชจ ๋ชจ๋…ธ๋ ˆํฌ ํ™˜๊ฒฝ์— ์ ํ•ฉํ•œ ์„ ํƒ์ž…๋‹ˆ๋‹ค. ESLint๋ฅผ ๋Œ€์ฒดํ•˜๊ธฐ๋ณด๋‹ค๋Š” ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ํšจ๊ณผ์ ์ด๋ฉฐ, ๋ณ‘ํ•ฉ ์ „ ๋‹จ๊ณ„์—์„œ ๋น ๋ฅธ ํ”ผ๋“œ๋ฐฑ์„ ์œ„ํ•ด์„œ๋Š” OxcLint๋ฅผ, ์ „์ฒด ๊ทœ์น™ ์ ์šฉ์„ ์œ„ํ•ด์„œ๋Š” ESLint๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

2๋‹จ๊ณ„: ํƒ€์ž… ๊ฒ€์‚ฌ, TypeScript ์ปดํŒŒ์ผ๋Ÿฌ

ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์ปดํŒŒ์ผ๋Ÿฌ(tsc)๋Š” ๋‹จ์ˆœํ•œ ๋นŒ๋“œ ๋„๊ตฌ๊ฐ€ ์•„๋‹ˆ๋ผ, ํƒ€์ž… ์ˆ˜์ค€์˜ ์ •ํ™•์„ฑ์„ ๊ฒ€์‚ฌํ•˜๋Š” ์ฃผ์š” ์ •์  ๋ถ„์„ ์—”์ง„์ž…๋‹ˆ๋‹ค. ์—„๊ฒฉ ๋ชจ๋“œ๋ฅผ ํ™œ์„ฑํ™”ํ•˜๋ฉด ์‹ค์ œ ํ™˜๊ฒฝ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋Œ€๋ถ€๋ถ„์˜ ๋ฒ„๊ทธ๋ฅผ ์žก์•„๋‚ด๋Š” ์„ค์ •์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

JSON

{
  "compilerOptions": {
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "exactOptionalPropertyTypes": true,
    "useUnknownInCatchVariables": true
  }
}

์„ธ๊ฒŒ ๋•Œ๋ฆฌ๋‹ค

# Type-check only, no emit -- ideal for CI
npx tsc --noEmit

noUnusedLocals noUnusedParameters ์ปดํŒŒ์ผ๋Ÿฌ ์ˆ˜์ค€์—์„œ ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ๋ณ€์ˆ˜์™€ ํ•จ์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ฐพ์•„๋ƒ…๋‹ˆ๋‹ค. useUnknownInCatchVariables (TypeScript 4.4 ์ด์ƒ)์—์„œ ์˜ˆ์™ธ๋ฅผ ํฌ์ฐฉํ•œ ์œ ํ˜•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. unknown ๋ณด๋‹ค๋Š” any์‚ฌ์šฉ ์ „์— ๋ช…์‹œ์ ์ธ ์œ ํ˜• ์ถ•์†Œ๋ฅผ ๊ฐ•์ œํ•ฉ๋‹ˆ๋‹ค.

TypeScript ์ œ์–ด ํ๋ฆ„ ๋ถ„์„ ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์กฐ๊ฑด ๋ถ„๊ธฐ ๋‚ด์—์„œ ํƒ€์ž…์„ ์ขํžˆ๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ œ๊ณตํ•˜๋Š” ๋‚ด์žฅ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ๋ณ„๋„์˜ ๋„๊ตฌ๊ฐ€ ์•„๋‹ˆ๋ฉฐ, strict ์ด ๋ชจ๋“œ๋Š” ์—„๊ฒฉํ•œ ๊ทœ์น™์ด ์ ์šฉ๋˜๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. ์ปดํŒŒ์ผ๋Ÿฌ์˜ ์ œ์–ด ํ๋ฆ„ ๋ถ„์„์€ ํƒ€์ž… ๊ฐ€๋“œ๋ฅผ ์ดํ•ดํ•ฉ๋‹ˆ๋‹ค. typeof ์ฒดํฌ ๋ฌด๋Šฌ, instanceof๊ทธ๋ฆฌ๊ณ  ์ฐจ๋ณ„์ ์ธ ๋…ธ๋™์กฐํ•ฉ ํŒจํ„ด.

tsc์˜ ํ•œ๊ณ„๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. TypeScript ์ปดํŒŒ์ผ๋Ÿฌ๋Š” ๋ณด์•ˆ ์ทจ์•ฝ์ , ์•„ํ‚คํ…์ฒ˜ ๊ฒฝ๊ณ„ ์œ„๋ฐ˜ ๋˜๋Š” ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ๋‚ด๋ณด๋‚ด๊ธฐ๋ฅผ ์ฐพ์•„๋‚ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํƒ€์ž… ์˜ค๋ฅ˜๋งŒ ์ฐพ์•„๋‚ด๋ฉฐ, ๊ทธ ์˜ค๋ฅ˜๋Š” ๋ช…ํ™•ํ•˜๊ฒŒ ์ง„๋‹จํ•ฉ๋‹ˆ๋‹ค.

3๋‹จ๊ณ„: ๋ณด์•ˆ, TypeScript์šฉ SAST

Semgrep: ํŒจํ„ด ๊ธฐ๋ฐ˜ ๋ณด์•ˆ ์Šค์บ๋‹

Semgrep์€ ์ฝ”๋“œ ํŒจํ„ด ๋งค์นญ์„ ํ†ตํ•ด ๋ณด์•ˆ ์ทจ์•ฝ์ ์„ ์ฐพ์•„๋ƒ…๋‹ˆ๋‹ค. TypeScript์˜ ๊ฒฝ์šฐ SQL ์ธ์ ์…˜, XSS, ํ•˜๋“œ์ฝ”๋”ฉ๋œ ์ž๊ฒฉ ์ฆ๋ช…, ์•ˆ์ „ํ•˜์ง€ ์•Š์€ ์ฝ”๋“œ ๋“ฑ์„ ํƒ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. eval ์‚ฌ์šฉ ๋ฐฉ์‹, ํ”„๋กœํ† ํƒ€์ž… ์˜ค์—ผ, ๊ทธ๋ฆฌ๊ณ  ์•ˆ์ „ํ•˜์ง€ ์•Š์€ Express.js ๊ตฌ์„ฑ ํŒจํ„ด์€ ์ปดํŒŒ์ผ์€ ์ œ๋Œ€๋กœ ๋˜์ง€๋งŒ ์•…์šฉ ๊ฐ€๋Šฅํ•œ ์ทจ์•ฝ์ ์„ ์œ ๋ฐœํ•ฉ๋‹ˆ๋‹ค.

์–Œ

# Custom rule: flag user input in SQL queries (TypeScript)
rules:
  - id: ts-sql-injection-risk
    patterns:
      - pattern: |
          const query = `SELECT ... ${$USER_INPUT} ...`;
    message: "User input directly interpolated into SQL -- use parameterized queries"
    languages: [typescript]
    severity: ERROR

์„ธ๊ฒŒ ๋•Œ๋ฆฌ๋‹ค

# Run with the community TypeScript security rules
semgrep scan --config=p/typescript --config=p/owasp-top-ten src/

Semgrep์€ ESLint์™€ ๊ฒฝ์Ÿํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์ƒํ˜ธ ๋ณด์™„์ ์ธ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ESLint๋Š” ๊ทœ์น™์„ ๊ฐ•์ œํ•˜๋Š” ๋ฐ˜๋ฉด, Semgrep์€ ๋ณด์•ˆ ์•ˆํ‹ฐ ํŒจํ„ด์„ ์ฐพ์•„๋ƒ…๋‹ˆ๋‹ค. ๋ณด์•ˆ์— ๊ด€์‹ฌ ์žˆ๋Š” ๋Œ€๋ถ€๋ถ„์˜ TypeScript ๊ฐœ๋ฐœํŒ€์€ ๋‘ ๋„๊ตฌ๋ฅผ ๋ชจ๋‘ ์‹คํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Snyk Code: IDE ํ†ตํ•ฉ ๊ธฐ๋Šฅ์„ ๊ฐ–์ถ˜ ML ๊ธฐ๋ฐ˜ SAST

Snyk Code๋Š” ๋จธ์‹ ๋Ÿฌ๋‹ ๊ธฐ๋ฐ˜ ๋ถ„์„ ์—”์ง„์„ ์‚ฌ์šฉํ•˜์—ฌ ํŒŒ์ผ ์ „์ฒด์˜ ์˜ค์—ผ ํ๋ฆ„์„ ์ถ”์ ํ•˜๋Š” SAST(์†Œํ”„ํŠธ์›จ์–ด ์•ˆ์ „ ํ…Œ์ŠคํŠธ)๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. VS Code ๋ฐ JetBrains IDE์— ํ†ตํ•ฉ๋˜์–ด ๊ฐœ๋ฐœ์ž๊ฐ€ CI ์‹คํ–‰์„ ๊ธฐ๋‹ค๋ฆด ํ•„์š” ์—†์ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋™์•ˆ ๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ”๋กœ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

์„ธ๊ฒŒ ๋•Œ๋ฆฌ๋‹ค

npm install --save-dev snyk
npx snyk auth
npx snyk code test

Snyk Code๋Š” ๊ฐœ๋ฐœ์ž ๊ฒฝํ—˜์— ์ค‘์ ์„ ๋‘๊ณ  IDE ๋‚ด์—์„œ ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•˜๋ฉฐ, ๊ฐ ๋ฐœ๊ฒฌ ์‚ฌํ•ญ์— ๋Œ€ํ•œ ์ˆ˜์ • ์ œ์•ˆ๊ณผ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์ œ์‹œํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ณด์•ˆ ํƒ์ง€๋งŒํผ ๋ณด์•ˆ ๊ต์œก์ด ์ค‘์š”ํ•œ ๊ฒฝ์šฐ์— ๋” ๋‚˜์€ ์„ ํƒ์ž…๋‹ˆ๋‹ค.

SonarQube์™€ SonarLint: ํ’ˆ์งˆ ๊ฒŒ์ดํŠธ ๋ฐ ํŠธ๋ Œ๋“œ ์ถ”์ 

SonarQube๋Š” ๋Œ€์‹œ๋ณด๋“œ, ํŠธ๋ Œ๋“œ ์ถ”์ , ํ’€ ๋ฆฌํ€˜์ŠคํŠธ ๋ฐ์ฝ”๋ ˆ์ด์…˜ ๊ธฐ๋Šฅ์„ ํ†ตํ•ด ์ง€์†์ ์ธ ์ฝ”๋“œ ํ’ˆ์งˆ ๋ถ„์„์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. TypeScript์˜ ๊ฒฝ์šฐ ๋ฒ„๊ทธ, ์ฝ”๋“œ ์Šค๋ฉœ, ๋ณด์•ˆ ์ทจ์•ฝ์ , ์ฝ”๋“œ ์ค‘๋ณต์„ ๊ฐ์ง€ํ•ฉ๋‹ˆ๋‹ค. SonarLint๋Š” SonarQube ๊ทœ์น™์„ IDE ๋‚ด์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ฃผ๋Š” ํ™•์žฅ ํ”„๋กœ๊ทธ๋žจ์ž…๋‹ˆ๋‹ค.

TypeScript ํŒ€์˜ ๊ฒฝ์šฐ SonarCloud(ํด๋ผ์šฐ๋“œ ํ˜ธ์ŠคํŒ… ๋ฒ„์ „)๊ฐ€ ๋” ๊ฐ„ํŽธํ•œ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ๊ณต๊ฐœ ์ €์žฅ์†Œ๋Š” ๋ฌด๋ฃŒ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, GitHub Actions ๋˜๋Š” GitLab CI๋ฅผ ํ†ตํ•ด ๋ช‡ ๋ถ„ ๋งŒ์— CI ํ†ตํ•ฉ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

์–Œ

# .github/workflows/sonar.yml
- name: SonarCloud Scan
  uses: SonarSource/sonarcloud-github-action@master
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

SonarQube๊ฐ€ ๋‹จ์ˆœํ•œ ๋ฆฐํŒ… ๊ธฐ๋Šฅ๋ณด๋‹ค ๋›ฐ์–ด๋‚œ ์ฃผ๋œ ์ด์œ ๋Š” ์ฝ”๋“œ ํ’ˆ์งˆ์ด ์‹œ๊ฐ„์ด ์ง€๋‚จ์— ๋”ฐ๋ผ ์–ด๋–ป๊ฒŒ ๋ณ€ํ™”ํ•˜๋Š”์ง€, ์–ด๋–ค ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ์ €ํ•˜๋˜๊ณ  ์žˆ๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ๊ฐ ํ’€ ๋ฆฌํ€˜์ŠคํŠธ์— ๋Œ€ํ•œ ์ƒˆ๋กœ์šด ์ฝ”๋“œ ํ’ˆ์งˆ ๊ฒŒ์ดํŠธ ์ ์ˆ˜๋Š” ์–ผ๋งˆ์ธ์ง€ ๋“ฑ์„ ๋ณด์—ฌ์ฃผ๋Š” ์ถ”์„ธ ๋ชจ๋ธ์— ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ด€๋ฆฌ์ž์šฉ ์ง€ํ‘œ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด SonarQube์˜ ๊ฐ•์ ์ด๋ฉฐ, ESLint๋‚˜ Semgrep์€ ์ด๋Ÿฌํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค.

4๋‹จ๊ณ„: ์•„ํ‚คํ…์ฒ˜ ๋ถ„์„

Dependency-Cruiser: ๋ชจ๋“ˆ ๊ฒฝ๊ณ„ ๊ทœ์น™ ์ ์šฉ

Dependency-Cruiser๋Š” ์ž„ํฌํŠธ ๊ทธ๋ž˜ํ”„๊ฐ€ ์ •์˜๋œ ์•„ํ‚คํ…์ฒ˜ ๊ทœ์น™์„ ๋”ฐ๋ฅด๋Š”์ง€ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค. ์‹œ๊ฐ์ ์ธ ์ข…์†์„ฑ ๊ทธ๋ž˜ํ”„๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ์ฝ”๋“œ๊ฐ€ ๋ชจ๋“ˆ ๊ฒฝ๊ณ„ ์ œ์•ฝ ์กฐ๊ฑด์„ ์œ„๋ฐ˜ํ•  ๊ฒฝ์šฐ CI๋ฅผ ์‹คํŒจ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

์ž๋ฐ” ์Šคํฌ๋ฆฝํŠธ

// .dependency-cruiser.cjs
module.exports = {
  forbidden: [
    {
      name: "no-circular",
      severity: "error",
      comment: "Circular dependencies make code hard to test and maintain",
      from: {},
      to: { circular: true },
    },
    {
      name: "no-ui-in-domain",
      severity: "error",
      comment: "Domain modules must not import from UI layer",
      from: { path: "^src/domain" },
      to: { path: "^src/ui" },
    },
    {
      name: "no-external-in-shared",
      severity: "warn",
      comment: "Shared utilities should minimize external dependencies",
      from: { path: "^src/shared" },
      to: { pathNot: "^(src|node_modules/(lodash|date-fns))" },
    },
  ],
};

์„ธ๊ฒŒ ๋•Œ๋ฆฌ๋‹ค

npx depcruise --validate .dependency-cruiser.cjs src/

Dependency-Cruiser๋Š” ๊ฒ€์ƒ‰ ์ฝ˜์†” ๋ฐ์ดํ„ฐ์—์„œ "react dependency analysis cli tool" ์ฟผ๋ฆฌ์— ์ง์ ‘์ ์œผ๋กœ ๋Œ€์‘ํ•˜๋ฉฐ, TypeScript/React ํ”„๋กœ์ ํŠธ์—์„œ ์˜์กด์„ฑ ๊ทธ๋ž˜ํ”„๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•˜๋Š” ํ‘œ์ค€ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.

Deptrac: ๋ ˆ์ด์–ด ๊ธฐ๋ฐ˜ ๊ฒฝ๊ณ„ ๊ฐ•์ œ ์ ์šฉ

Deptrac์€ ์•„ํ‚คํ…์ฒ˜ ๊ณ„์ธต์„ ๊ฐ•์ œํ•˜์—ฌ ์˜๊ตฌ ์ €์žฅ ์ฝ”๋“œ๊ฐ€ ํ”„๋ ˆ์  ํ…Œ์ด์…˜์—์„œ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†๋„๋ก ํ•˜๊ณ , ๋„๋ฉ”์ธ ๊ฐ์ฒด๊ฐ€ ์ธํ”„๋ผ์— ์˜์กดํ•˜์ง€ ์•Š๋„๋ก ํ•˜๋ฉฐ, ์ „์ฒด ์ฝ”๋“œ๋ฒ ์ด์Šค์—์„œ ๋ชจ๋“ˆ ๊ฒฝ๊ณ„๊ฐ€ ์ค€์ˆ˜๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

์–Œ

# deptrac.yaml
parameters:
  layers:
    - name: Domain
      collectors:
        - type: directory
          value: src/domain
    - name: Application
      collectors:
        - type: directory
          value: src/application
    - name: Infrastructure
      collectors:
        - type: directory
          value: src/infrastructure
  ruleset:
    Domain:
      - ~Infrastructure  # Domain must not depend on Infrastructure
    Application:
      - Domain
    Infrastructure:
      - Application
      - Domain

Deptrac์€ ๋ ˆ์ด์–ด ๋ถ„๋ฆฌ๊ฐ€ ๋‹จ์ˆœํ•œ ์„ ํ˜ธ๊ฐ€ ์•„๋‹Œ ์„ค๊ณ„ ์ œ์•ฝ ์กฐ๊ฑด์ธ ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜, DDD ๋˜๋Š” ํ—ฅ์‚ฌ๊ณ ๋„ ์•„ํ‚คํ…์ฒ˜ ํŒจํ„ด์„ ๋”ฐ๋ฅด๋Š” ํ”„๋กœ์ ํŠธ์—์„œ ๊ฐ€์žฅ ํฐ ๊ฐ€์น˜๋ฅผ ๋ฐœํœ˜ํ•ฉ๋‹ˆ๋‹ค.

Nx: ๋ชจ๋…ธ๋ ˆํฌ ์ˆ˜์ค€์˜ ์ข…์†์„ฑ ๊ด€๋ฆฌ

TypeScript ๋ชจ๋…ธ๋ ˆํฌ์˜ ๊ฒฝ์šฐ, Nx๋Š” ์ €์žฅ์†Œ์˜ ๋ชจ๋“  ํ”„๋กœ์ ํŠธ์— ๊ฑธ์ณ ๋ชจ๋“ˆ ๊ฒฝ๊ณ„ ๊ฐ•์ œ, ์˜ํ–ฅ์„ ๋ฐ›๋Š” ๋นŒ๋“œ ๊ฐ์ง€ ๋ฐ ์ข…์†์„ฑ ๊ทธ๋ž˜ํ”„ ์‹œ๊ฐํ™”๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

JSON

// .eslintrc.json -- Nx module boundary rules
{
  "rules": {
    "@nx/enforce-module-boundaries": [
      "error",
      {
        "allow": [],
        "depConstraints": [
          { "sourceTag": "scope:shared", "onlyDependOnLibsWithTags": ["scope:shared"] },
          { "sourceTag": "scope:feature", "onlyDependOnLibsWithTags": ["scope:shared", "scope:feature"] },
          { "sourceTag": "scope:app", "onlyDependOnLibsWithTags": ["scope:shared", "scope:feature"] }
        ]
      }
    ]
  }
}

Nx์˜ affected ์ด ๋ช…๋ น์–ด๋Š” ๋ณ€๊ฒฝ๋œ ๋ชจ๋“ˆ๊ณผ ๊ด€๋ จ๋œ ํ…Œ์ŠคํŠธ ๋ฐ ๋ฆฐํŒ…๋งŒ ์‹คํ–‰ํ•˜์—ฌ ๋Œ€๊ทœ๋ชจ ๋ชจ๋…ธ๋ ˆํฌ์—์„œ CI ์‹œ๊ฐ„์„ ํฌ๊ฒŒ ๋‹จ์ถ•ํ•ฉ๋‹ˆ๋‹ค.

5๊ณ„์ธต: ๋ฐ๋“œ ์ฝ”๋“œ ๊ฐ์ง€

Knip: ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ๋‚ด๋ณด๋‚ด๊ธฐ, ํŒŒ์ผ ๋ฐ ์ข…์†์„ฑ ์ฐพ๊ธฐ

Knip์€ ์ „์ฒด ๋ชจ๋“ˆ ๊ทธ๋ž˜ํ”„๋ฅผ ๋ถ„์„ํ•˜์—ฌ ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ๋‚ด๋ณด๋‚ด๊ธฐ, ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ํŒŒ์ผ ๋ฐ ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ํ•ญ๋ชฉ์„ ์‹๋ณ„ํ•ฉ๋‹ˆ๋‹ค. package.json ์˜์กด์„ฑ, ESLint๊ฐ€ ์ •์˜ํ•˜๋Š” ์ฝ”๋“œ ๋ˆ„์  ์œ ํ˜• no-unused-vars ํŒŒ์ผ ๋‚ด๋ถ€๋งŒ ํ™•์ธํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํฌ์ฐฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

์„ธ๊ฒŒ ๋•Œ๋ฆฌ๋‹ค

npm install --save-dev knip
npx knip

JSON

// knip.json
{
  "entry": ["src/index.ts", "src/**/*.test.ts"],
  "project": ["src/**/*.ts"],
  "ignore": ["src/generated/**"],
  "ignoreDependencies": ["vitest"]
}

Knip์€ SC ๋ฐ์ดํ„ฐ์—์„œ "dead code detection unused exports javascript typescript"๋กœ ์ง์ ‘ ๊ฒ€์ƒ‰๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” TypeScript ํ”„๋กœ์ ํŠธ์—์„œ ๋ชจ๋“ˆ ์ˆ˜์ค€์˜ ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ์ฝ”๋“œ(dead code)๋ฅผ ์‹๋ณ„ํ•˜๋Š” ๋ฐ ์žˆ์–ด ํ˜„์žฌ ๊ฐ€์žฅ ๋›ฐ์–ด๋‚œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.

๋ ˆ์ด์–ด 6: ํ”„๋กœ๊ทธ๋žจ ๋ถ„์„, ts-morph

ts-morph๋Š” TypeScript ์ปดํŒŒ์ผ๋Ÿฌ API ๋ž˜ํผ๋กœ, TypeScript AST์— ๋Œ€ํ•œ ์‚ฌ์šฉ์ž ์ง€์ • ๋ถ„์„, ์ฝ”๋“œ ์ˆ˜์ • ๋ฐ ๋„๊ตฌ๋ฅผ ์‰ฝ๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค๋‹ˆ๋‹ค. SC ๋ฐ์ดํ„ฐ์—์„œ "ts-morph" ๋˜๋Š” "ts morph"๋กœ ์ง์ ‘ ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํƒ€์ดํ”„ ์Šคํฌ๋ฆฝํŠธ

import { Project } from "ts-morph";

const project = new Project({ tsConfigFilePath: "tsconfig.json" });

// Find all async functions that never await anything
const suspiciousAsyncFns: string[] = [];

for (const sourceFile of project.getSourceFiles()) {
  for (const fn of sourceFile.getFunctions()) {
    if (fn.isAsync()) {
      const hasAwait = fn.getDescendantsOfKind(
        SyntaxKind.AwaitExpression
      ).length > 0;
      if (!hasAwait) {
        suspiciousAsyncFns.push(
          `${sourceFile.getFilePath()}:${fn.getName() ?? "anonymous"}`
        );
      }
    }
  }
}

console.log("Async functions with no await:", suspiciousAsyncFns);

ts-morph๋Š” ๋ฐ”๋กœ ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ๋ถ„์„ ๋„๊ตฌ๊ฐ€ ์•„๋‹ˆ๋ผ, ๊ทธ๋Ÿฌํ•œ ๋„๊ตฌ๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. ๊ธฐ์กด ๋„๊ตฌ์—์„œ ์ง€์›ํ•˜๋Š” ๋ฒ”์œ„๋ฅผ ๋„˜์–ด์„œ๋Š” ๋งž์ถคํ˜• ๋ถ„์„์ด ํ•„์š”ํ•œ ํŒ€, ์˜ˆ๋ฅผ ๋“ค์–ด ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์Šคํฌ๋ฆฝํŠธ, ๋งž์ถคํ˜• ์•„ํ‚คํ…์ฒ˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ธฐ, ์ž๋™ ๋ฆฌํŒฉํ† ๋ง ๋˜๋Š” ์ฝ”๋“œ ์ƒ์„ฑ ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์ถ•์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.

TypeScript ๋น„๋™๊ธฐ/Await ์ •์  ๋ถ„์„

๊ฒ€์ƒ‰ ์ฝ˜์†” ๋ฐ์ดํ„ฐ๋Š” "typescript ์ •์  ๋ถ„์„ async await ๋…ผ๋ฌธ ๋„๊ตฌ" ๋ฐ "typescript ์ •์  ๋ถ„์„ async await ์ทจ์•ฝ์  ๋…ผ๋ฌธ ๋„๊ตฌ"์™€ ๊ด€๋ จ๋œ ํŠน์ • ๊ฒ€์ƒ‰์–ด ํด๋Ÿฌ์Šคํ„ฐ๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ์ด๋Š” ์‹ค๋ฌด์ž๋“ค์ด ํƒ€์ž… ์ˆ˜์ค€์—์„œ ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์˜ค๋ฅ˜๋ฅผ ์ดํ•ดํ•˜๋Š” ๋„๊ตฌ๋ฅผ ์ฐพ๊ณ  ์žˆ์Œ์„ ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค.

์‹ค์ œ TypeScript ๊ฐœ๋ฐœํŒ€์—๊ฒŒ ์‹ค์งˆ์ ์ธ ํ•ด๊ฒฐ์ฑ…์€ typescript-eslint์˜ ๋น„๋™๊ธฐ ๊ด€๋ จ ๊ทœ์น™ ๋„ค ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค.no-floating-promises, await-thenable, no-misused-promises, require-await)์™€ ๊ฒฐํ•ฉ strictNullChecks useUnknownInCatchVariables์ด๋“ค์„ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ํ•™์ˆ  ์—ฐ๊ตฌ ๋„๊ตฌ ์—†์ด๋„ ๊ฐ€์žฅ ํ”ํ•œ ๋น„๋™๊ธฐ ์œ ํ˜• ์˜ค๋ฅ˜๋ฅผ ์žก์•„๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋น„๋™๊ธฐ ์ทจ์•ฝ์  ํŒจํ„ด์— ๋Œ€ํ•œ ๋ณด์•ˆ ์—ฐ๊ตฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ํŒ€์—๊ฒŒ TAJS์™€ Jelly๋Š” ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์˜ ๋น„๋™๊ธฐ ์‹คํ–‰ ์˜๋ฏธ๋ก ์„ ๋ชจ๋ธ๋งํ•˜๋Š” ํ•™์ˆ ์ ์ธ ์ •์  ๋ถ„์„ ๋„๊ตฌ์ด์ง€๋งŒ, ์ด๋Š” ์—ฐ๊ตฌ ๋„๊ตฌ์ผ ๋ฟ ์‹ค์ œ ๊ฐœ๋ฐœ ๋„๊ตฌ๋Š” ์•„๋‹™๋‹ˆ๋‹ค.

Angular ๋ฐ React: ํ”„๋ ˆ์ž„์›Œํฌ๋ณ„ ์ •์  ๋ถ„์„

Angular ๊ฐœ๋ฐœํŒ€์„ ์œ„ํ•œ ์ •๋ณด์ž…๋‹ˆ๋‹ค., @angular-eslint Angular์— ํŠนํ™”๋œ ์ปดํฌ๋„ŒํŠธ ํŒจํ„ด, ํ…œํ”Œ๋ฆฟ ๋ถ„์„ ๋ฐ ์„œ๋น„์Šค ์ฃผ์ž… ๊ด€๋ จ ๋ฆฐํŒ… ๊ทœ์น™์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์œ„์˜ ESLint + typescript-eslint ์„ค์ •๊ณผ ํ†ตํ•ฉ๋ฉ๋‹ˆ๋‹ค.

React ํŒ€์„ ์œ„ํ•ด ๋ฐธ๋ฆฌ eslint-plugin-react, eslint-plugin-react-hooks์˜ˆ์‚ฐ ๋ฐ eslint-plugin-jsx-a11y ํ”Œ๋Ÿฌ๊ทธ์ธ์€ React ๊ด€๋ จ ํŒจํ„ด์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค. Dependency-Cruiser๋Š” React ๊ด€๋ จ ์˜์กด์„ฑ ๊ทธ๋ž˜ํ”„ ๋ถ„์„์„ ์ฒ˜๋ฆฌํ•˜๋ฉฐ, SC ๋ฐ์ดํ„ฐ์—์„œ "react dependency analysis cli tool" ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.

IDE ํ†ตํ•ฉ: VS Code ๋ฐ JetBrains์šฉ ๋„๊ตฌ

"VS Code ํ†ตํ•ฉ์„ ์œ„ํ•œ ์ตœ๊ณ ์˜ ์ฝ”๋“œ ํ’ˆ์งˆ ๋„๊ตฌ" ๋ฐ "IDE ํ†ตํ•ฉ์„ ์œ„ํ•œ ์ตœ๊ณ ์˜ ์ฝ”๋“œ ํ’ˆ์งˆ ๋„๊ตฌ"๋ผ๋Š” ๊ฒ€์ƒ‰์–ด๋Š” ์‹ค์ œ์ ์ธ ํ•„์š”์„ฑ์„ ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค. CI ํ”ผ๋“œ๋ฐฑ์€ ๋™์ž‘์„ ๋ณ€๊ฒฝํ•˜๊ธฐ์—๋Š” ๋„ˆ๋ฌด ๋Šฆ๊ฒŒ ๋„์ฐฉํ•ฉ๋‹ˆ๋‹ค. IDE ํ†ตํ•ฉ ๋ถ„์„์€ ๊ฐ€์žฅ ๋น ๋ฅธ ํ”ผ๋“œ๋ฐฑ ๋ฃจํ”„๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

VS Code : rust-analyzer Clippy์™€ ๋™๋“ฑํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” ESLint ํ™•์žฅ ํ”„๋กœ๊ทธ๋žจ, SonarQube ๊ทœ์น™์„ ์ธ๋ผ์ธ์œผ๋กœ ํ‘œ์‹œํ•˜๋Š” SonarLint ํ™•์žฅ ํ”„๋กœ๊ทธ๋žจ, ๋ณด์•ˆ ์ทจ์•ฝ์ ์„ ์•Œ๋ ค์ฃผ๋Š” Snyk ํ™•์žฅ ํ”„๋กœ๊ทธ๋žจ, ๊ทธ๋ฆฌ๊ณ  ํƒ€์ž… ์ˆ˜์ค€ ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•˜๋Š” ๋‚ด์žฅ TypeScript ์–ธ์–ด ์„œ๋น„์Šค๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

JetBrains(WebStorm/IntelliJ) : WebStorm์€ ํƒ€์ž… ์˜ค๋ฅ˜๋ฅผ ์ฝ”๋“œ ๋‚ด์—์„œ ๋ฐ”๋กœ ํ‘œ์‹œํ•ด์ฃผ๋Š” TypeScript ์ง€์› ๊ธฐ๋Šฅ๊ณผ ESLint, Prettier, SonarLint์™€์˜ ํ†ตํ•ฉ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋‚ด์žฅ๋œ ๊ฒ€์‚ฌ ์‹œ์Šคํ…œ์€ ESLint ๊ทœ์น™์—์„œ ๋‹ค๋ฃจ๋Š” ๋งŽ์€ ํŒจํ„ด์„ ๋™์ผํ•˜๊ฒŒ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค.

VS Code์—์„œ TypeScript ์ •์  ๋ถ„์„ ๋ฒ”์œ„๋ฅผ ์ตœ๋Œ€ํ™”ํ•˜๊ธฐ ์œ„ํ•œ ์„ค์ • :

JSON

// .vscode/settings.json
{
  "typescript.tsdk": "node_modules/typescript/lib",
  "typescript.enablePromptUseWorkspaceTsdk": true,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  },
  "eslint.validate": ["javascript", "typescript", "typescriptreact"],
  "sonarlint.connectedMode.project": {
    "connectionId": "my-sonarcloud",
    "projectKey": "my-org_my-project"
  }
}

๋ฐฉ๋ฒ• SMART TS XL TypeScript ๋ถ„์„ ๊ธฐ๋Šฅ์„ ๊ธฐ์—… ์ „์ฒด๋กœ ํ™•์žฅํ•ฉ๋‹ˆ๋‹ค.

์œ„์˜ ๋„๊ตฌ๋“ค์€ TypeScript ํ”„๋กœ์ ํŠธ ๋‚ด์˜ TypeScript ์ฝ”๋“œ๋งŒ์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค. TypeScript ์„œ๋น„์Šค๊ฐ€ COBOL ๋ฐฐ์น˜ ํ”„๋กœ๊ทธ๋žจ, Java API, Python ๋ฐ์ดํ„ฐ ํŒŒ์ดํ”„๋ผ์ธ ๋˜๋Š” ๊ธฐ์กด ๋ฉ”์ธํ”„๋ ˆ์ž„ ์‹œ์Šคํ…œ๊ณผ ์ƒํ˜ธ ์ž‘์šฉํ•˜๋Š” ์กฐ์ง์—์„œ๋Š” ๋‹จ์ผ ์–ธ์–ด ๋„๊ตฌ๋กœ๋Š” ์–ธ์–ด ๊ฒฝ๊ณ„๋ฅผ ๋„˜๋‚˜๋“œ๋Š” ์ข…์†์„ฑ์„ ํŒŒ์•…ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

SMART TS XL์˜ ์ •์  ์ฝ”๋“œ ๋ถ„์„ TypeScript๋Š” COBOL, JCL, Java, Python, RPG, SQL ๋“ฑ ๊ธฐ์—… ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๋ชจ๋“  ์–ธ์–ด์™€ ํ•จ๊ป˜ ๋‹ค๋ค„์ง€๋ฉฐ, ์ด๋Ÿฌํ•œ ์–ธ์–ด๋“ค ์ „๋ฐ˜์— ๊ฑธ์ณ ํ†ตํ•ฉ๋œ ์ข…์†์„ฑ ๋ชจ๋ธ์„ ๊ตฌ์ถ•ํ•ฉ๋‹ˆ๋‹ค. TypeScript ์„œ๋น„์Šค๊ฐ€ COBOL ํ”„๋กœ๊ทธ๋žจ์œผ๋กœ ์ง€์›๋˜๋Š” API๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ, SMART TS XL ๊ทธ ๊ด€๊ณ„๋ฅผ ์ถ”์ ํ•˜์—ฌ ํฌํ•จ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ํ–ฅ ๋ถ„์„ ์–ด๋–ค ๊ตฌ์„ฑ ์š”์†Œ๋ผ๋„ ๋ณ€๊ฒฝํ•˜๊ธฐ ์ „์—.

์—”ํ„ฐํ”„๋ผ์ด์ฆˆ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์„ ํ†ตํ•ด ์ „์ฒด ๋‹ค๊ตญ์–ด ์ฝ”๋“œ๋ฒ ์ด์Šค๋ฅผ ์ฟผ๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠน์ • ๋ชจ๋“ˆ์˜ ๋ชจ๋“  TypeScript ๊ฐ€์ ธ์˜ค๊ธฐ, TypeScript ์„œ๋น„์Šค๊ฐ€ ํ˜ธ์ถœํ•˜๋Š” ๋ชจ๋“  Java ๋ฉ”์„œ๋“œ, TypeScript์—์„œ ์‚ฌ์šฉํ•˜๋Š” API์— ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋ชจ๋“  COBOL ์นดํ”ผ๋ถ ๋“ฑ์„ ์ˆ˜๋ฐฑ๋งŒ ์ค„์˜ ์ฝ”๋“œ์—์„œ ์–ด๋–ค ์–ธ์–ด ์กฐํ•ฉ์œผ๋กœ๋“  ๋ช‡ ์ดˆ ๋งŒ์— ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋” ํฐ ํฌํŠธํด๋ฆฌ์˜ค ๋‚ด์—์„œ TypeScript๋ฅผ ํ•˜๋‚˜์˜ ์–ธ์–ด๋กœ ์‚ฌ์šฉํ•˜๋Š” ์—”ํ„ฐํ”„๋ผ์ด์ฆˆ ํŒ€์˜ ๊ฒฝ์šฐ, SMART TS XL ๊ฑด์ถ•์  ๊ฐ€์‹œ์„ฑ๊ณผ ๋‹ค๊ตญ์–ด ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ข…์†์„ฑ ๋งคํ•‘ ์ด๋ฅผ ํ†ตํ•ด TypeScript ์ „์šฉ ๋„๊ตฌ๋Š” ์‹œ์Šคํ…œ์˜ ํ•ด๋‹น ๋ถ€๋ถ„์„ ์‚ดํŽด๋ณด๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. SMART TS XL ์ „์ฒด๋ฅผ ๋ณธ๋‹ค.

์ ํ•ฉํ•œ ์Šคํƒ ๊ตฌ์ถ•

TypeScript์˜ ๋ชจ๋“  ํ’ˆ์งˆ ๊ธฐ์ค€์„ ์ถฉ์กฑํ•˜๋Š” ๋‹จ์ผ ๋„๊ตฌ๋Š” ์—†์Šต๋‹ˆ๋‹ค. ์˜ฌ๋ฐ”๋ฅธ ์ ‘๊ทผ ๋ฐฉ์‹์€ ๊ณ„์ธตํ™”์ด๋ฉฐ, ๊ฐ ๊ณ„์ธต์€ ์„œ๋กœ ๋‹ค๋ฅธ ์œ ํ˜•์˜ ๋ฌธ์ œ๋ฅผ ๋ชฉํ‘œ๋กœ ํ•ฉ๋‹ˆ๋‹ค.

์ตœ์†Œ ํ•„์ˆ˜ ๊ธฐ์ˆ  ์Šคํƒ (์ƒˆ ํ”„๋กœ์ ํŠธ, ์†Œ๊ทœ๋ชจ ํŒ€):

  • ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์—„๊ฒฉ ๋ชจ๋“œ
  • typescript-eslint ๋น„๋™๊ธฐ ๊ทœ์น™์ด ํฌํ•จ๋œ ESLint
  • npm audit ์˜์กด์„ฑ ์ทจ์•ฝ์ ์˜ ๊ฒฝ์šฐ

์šด์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์Šคํƒ (์ค‘๊ทœ๋ชจ ํŒ€, ๋ณด์•ˆ ์ค‘์š”๋„ ๊ณ ๋ ค):

  • ์œ„์˜ ๋ชจ๋“  ๊ฒƒ ์™ธ์—๋„
  • Biome ๋˜๋Š” Prettier๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ˜•์‹์„ ์ง€์ •ํ•˜์„ธ์š”.
  • ๋ณด์•ˆ SAST์šฉ Semgrep
  • ์ฃฝ์€ ์ฝ”๋“œ์šฉ Knip
  • SonarCloud๋ฅผ ํ™œ์šฉํ•œ ํ’ˆ์งˆ ์ถ”์„ธ ํŒŒ์•…

์—”ํ„ฐํ”„๋ผ์ด์ฆˆ/๋ชจ๋…ธ๋ ˆํฌ ์Šคํƒ (๋Œ€๊ทœ๋ชจ ํŒ€, ์•„ํ‚คํ…์ฒ˜ ์ œ์•ฝ ์กฐ๊ฑด):

  • ์œ„์˜ ๋ชจ๋“  ๊ฒƒ ์™ธ์—๋„
  • ๋ชจ๋“ˆ ๊ฒฝ๊ณ„ ๊ฐ•์ œ๋ฅผ ์œ„ํ•œ Dependency-Cruiser
  • ๋ชจ๋…ธ๋ ˆํฌ ์˜ํ–ฅ ๋นŒ๋“œ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•œ Nx
  • ๋ ˆ์ด์–ด ๊ฒฝ๊ณ„ ๊ฒ€์ฆ์„ ์œ„ํ•œ Deptrac
  • ์˜จํ”„๋ ˆ๋ฏธ์Šค ํ’ˆ์งˆ ๊ฒŒ์ดํŠธ์šฉ SonarQube(์ž์ฒด ํ˜ธ์ŠคํŒ…)

๋‹ค๊ตญ์–ด ์—”ํ„ฐํ”„๋ผ์ด์ฆˆ ์Šคํƒ (TypeScript์™€ ๊ธฐ์กด ์‹œ์Šคํ…œ ๋ณ‘์ž…):

  • ์œ„์˜ ๋ชจ๋“  ๊ฒƒ ์™ธ์—๋„
  • SMART TS XL ์–ธ์–ด ๊ฐ„ ์˜ํ–ฅ ๋ถ„์„ ๋ฐ ์˜์กด์„ฑ ๋งคํ•‘์„ ์œ„ํ•ด