Tslint: linting vue/html ํŒŒ์ผ ์ง€์›

์— ๋งŒ๋“  2017๋…„ 01์›” 22์ผ  ยท  35์ฝ”๋ฉ˜ํŠธ  ยท  ์ถœ์ฒ˜: palantir/tslint

๊ธฐ๋Šฅ ์š”์ฒญ

  • __TSLint ๋ฒ„์ „__: 4.3.1
  • __TypeScript ๋ฒ„์ „__: 2.1.5
  • ____๋ฅผ ํ†ตํ•ด TSLint ์‹คํ–‰__: Node.js API

๋ฆฐํŠธ๋˜๋Š” TypeScript ์ฝ”๋“œ

<template>
  <q-layout>
    <div slot="header" class="toolbar">
      <q-toolbar-title :padding="0">
        Quasar Framework v{{quasarVersion}}
      </q-toolbar-title>
    </div>

    <!--
      Replace following "div" with
      "<router-view class="layout-view">" component
      if using subRoutes
    -->
    <div class="layout-view">
      <div class="logo-container non-selectable no-pointer-events">
        <div class="logo" :style="position">
          <img src="~assets/quasar-logo.png">
          <p class="caption text-center">
            <span class="desktop-only">Move your mouse!!!.</span>
            <span class="touch-only">Touch screen and move.</span>
          </p>
        </div>
      </div>
    </div>
  </q-layout>
</template>

<script lang="ts">
  import * as Quasar from 'quasar';
  import { Utils } from 'quasar';
  import * as Vue from 'vue';
  import Component from 'vue-class-component';

  const moveForce = 30;
  const rotateForce = 40;

  @Component({
  })
  export default class Index extends Vue {
    rotateX: number;
    rotateY: number;
    moveY: number;
    moveX: number;
    quasarVersion: string;
   ...  
 }
</script>

<style lang="stylus">
....
</style>

tslint.json ๊ตฌ์„ฑ:

{
  "rules": {
    "class-name": true,
    "curly": true,
    "eofline": false,
    "expr" : true,
    "forin": true,
    "indent": [true, "spaces"],
    "label-position": true,
    "label-undefined": true,
    "max-line-length": [true, 140],
    "no-arg": true,
    "no-bitwise": true,
    "no-console": [true,
      "debug",
      "info",
      "time",
      "timeEnd",
      "trace"
    ],
    "no-construct": true,
    "no-debugger": true,
    "no-duplicate-key": true,
    "no-duplicate-variable": true,
    "no-empty": true,
    "no-eval": true,
    "no-string-literal": false,
    "no-switch-case-fall-through": true,
    "no-trailing-comma": true,
    "no-trailing-whitespace": true,
    "no-unused-expression": false,
    "no-unused-variable": true,
    "no-unreachable": true,
    "no-use-before-declare": true,
    "one-line": [true,
      "check-open-brace",
      "check-catch",
      "check-else",
      "check-whitespace"
    ],
    "quotemark": [true, "single"],
    "radix": false,
    "semicolon": [false],
    "triple-equals": [true, "allow-null-check"],
    "variable-name": false,
    "whitespace": [true,
      "check-branch",
      "check-decl",
      "check-operator",
      "check-separator",
      "check-type"
    ]
  }
}

์‹ค์ œ ํ–‰๋™

์ „์ฒด ํŒŒ์ผ์„ ๋ฆฐํŠธํ•˜๋ ค๊ณ  ์‹œ๋„ํ–ˆ์ง€๋งŒ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ์ƒ๋˜๋Š” ํ–‰๋™

eslint์™€ ์œ ์‚ฌํ•˜๊ฒŒ ์Šคํฌ๋ฆฝํŠธ ํƒœ๊ทธ ๋‚ด๋ถ€์˜ ์ฝ”๋“œ๋งŒ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

Aged Away Feature Request

๊ฐ€์žฅ ์œ ์šฉํ•œ ๋Œ“๊ธ€

์—ฌ๊ธฐ์— ๋” ๋งŽ์€ ์†Œ์‹์ด ์žˆ์Šต๋‹ˆ๊นŒ? ๐Ÿค™

๋ชจ๋“  35 ๋Œ“๊ธ€

@nrip-monotype, vue-loader ๊ตฌ์„ฑํ•˜์—ฌ Vue ๋‹จ์ผ ํŒŒ์ผ ๊ตฌ์„ฑ ์š”์†Œ์™€ ํ•จ๊ป˜ TSLint๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์›นํŒฉ ์˜ˆ๋ฅผ ๋“ค์–ด 2 Iํ•ฉ๋‹ˆ๋‹ค (ํ•œ ๋ฒˆ ๋ด ๊ฐ€์ง€๊ณ  ์ด๋Ÿฐ ์‹์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ 

module: {
        rules: [
            {
                enforce: 'pre',
                test: /\.ts$/,
                loader: 'tslint-loader',
                exclude: /(node_modules)/,
                options: {
                    configFile: 'tslint.json'
                }
            },
            {
                test: /\.ts$/,
                exclude: /node_modules|vue\/src/,
                loader: 'ts-loader',
                options: {
                    appendTsSuffixTo: [/\.vue$/],
                    transpileOnly: true,
                    isolatedModules: true
                }
            },
            {
                test: /\.vue$/,
                loader: 'vue-loader',
                options: {
                    loaders: {
                        ts: 'ts-loader!tslint-loader'
                    }
                }
            },
        }
    }
}

@romandragan , ๊ท€ํ•˜์˜ ์ œ์•ˆ์€ ํšจ๊ณผ๊ฐ€ ์žˆ์ง€๋งŒ no-consecutive-blank-lines ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๋งˆํฌ์—… ์š”์†Œ๊ฐ€ ๋นˆ ์ค„๋กœ ๋Œ€์ฒด๋˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

<template>
   <span class='hello'>hello world</span>
</tempalte>
<script lang="ts">
    return {};
</script>
<style>
    .hello { background-color: pink }
</style>

tslint๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

    return {};




"no-consecutive-blank-lines": [true, 3] ํ•˜๋ฉด ์‹คํŒจํ•˜์ง€๋งŒ "no-consecutive-blank-lines": [true, 4] ๋Š” ์„ฑ๊ณตํ•ฉ๋‹ˆ๋‹ค... ์ด ๋ฌธ์ œ๋ฅผ ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์•„์‹ญ๋‹ˆ๊นŒ? (์„ ํ–‰ ๋ฐ ํ›„ํ–‰ ๊ณต๋ฐฑ์„ ์ œ๊ฑฐํ•˜๋Š” ๋‹ค๋ฅธ ํ”Œ๋Ÿฌ๊ทธ์ธ์˜ ์•ฝ์ž...)

@lucastheisen , ๋‚˜์—๊ฒŒ๋„ ๊ฐ™์€ ๋ฌธ์ œ ๐Ÿ˜ž ํ•ด๊ฒฐ์ฑ…์„ ์ฐพ๋Š” ์ค‘...

@romandragan ์˜ ์„ค์ •์ด ์ €์—๊ฒŒ typeCheck ํ”Œ๋ž˜๊ทธ๊ฐ€ vue-loader์—์„œ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. vue-loader ์™ธ๋ถ€์—์„œ ์ •์ƒ์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

>

module: {
        rules: [
            {
                enforce: 'pre',
                test: /\.ts$/,
                loader: 'tslint-loader',
                exclude: /(node_modules)/,
                options: {
                    configFile: 'tslint.json'
                }
            },
            {
                test: /\.ts$/,
                exclude: /node_modules|vue\/src/,
                loader: 'ts-loader',
                options: {
                    appendTsSuffixTo: [/\.vue$/],
                    transpileOnly: true,
                    isolatedModules: true,
                    typeCheck: true // This is ok.
                }
            },
            {
                test: /\.vue$/,
                loader: 'vue-loader',
                options: {
                    loaders: {
                        ts: 'ts-loader!tslint-loader' // Can't append `?typeCheck` here.
                    }
                }
            },
        }
    }
}

vue-loader์—์„œ ์œ ํ˜• ๊ฒ€์‚ฌ ๊ทœ์น™์„ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

ERROR in ./app.ts
(10,29): error TS2307: Cannot find module './components/sidebar.vue'.

ERROR in ./~/ts-loader!./~/tslint-loader?formatter=verbose&typeCheck!./~/vue-loader/lib/selector.js?type=script&index=0!./components/sidebar.vue
Module build failed: Error:
Invalid source file: /absolute/path/to/sidebar.vue. Ensure that the files supplied to lint have a .ts, .tsx, .js or .jsx extension.

@lucastheisen ๋ฐ @romandragan๊ณผ ๋™์ผํ•œ no-consecutive-blank-lines ๊ฐ€์–‘์„ฑ์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

์ด ๊ตฌ์„ฑ์„ ์‚ฌ์šฉํ•  ๋•Œ ERROR in Entry module not found: Error: Can't resolve 'ts-loader!tslint-loader' ๋ฉ๋‹ˆ๋‹ค.

            {
                test: /\.vue$/,
                loader: 'vue-loader',
                options: {
                    loaders: {
                        ts: 'ts-loader!tslint-loader'
                    }
                }
            },

@ARomancev , ์–ด๋–ค ๋ฒ„์ „์˜ ์›นํŒฉ์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๊นŒ? ts-loader ๋ฐ tslint-loader npm ๋ชจ๋“ˆ์ด ์„ค์น˜๋˜์–ด ์žˆ์Šต๋‹ˆ๊นŒ?

@romandragan , ๋‹ค์Œ์€ ๋‹ค์Œ์„ ์‚ฌ์šฉํ•˜๋Š” npm ๊ตฌ์„ฑ์ž…๋‹ˆ๋‹ค.
```
"tslint": "^5.1.0",
"ts-loader": "^2.0.3",
"์›นํŒฉ": "^2.4.1",

And this is the webpack config:
  {
    test: /\.tsx?$/,
    enforce: 'pre',
    loader: 'tslint-loader'
  },
  {
    test: /\.tsx?$/,
    loader: 'ts-loader',
    exclude: /node_modules/,
    options: {
      appendTsSuffixTo: [/\.vue$/]
    }
  },

```
ts-loader์™€ tslint-loader๋Š” ๋ชจ๋‘ *.ts ํŒŒ์ผ์—์„œ ์˜ฌ๋ฐ”๋ฅธ lint๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ๋•Œ๋ฌธ์— ์„ค์น˜๋˜๊ณ  ์ œ๋Œ€๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์œผ๋กœ .vue ํŒŒ์ผ์—์„œ TypeScript ๋กœ์ง์„ ๋ถ„๋ฆฌํ•˜๊ณ  vue ๋กœ๋”์—์„œ tslint-loader ๋ฅผ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด no-consecutive-blank-lines ์‚ฌ๋ผ์ง‘๋‹ˆ๋‹ค.

@romandragan ์ด๊ฒƒ์€ ์ž‘๋™ํ•˜์ง€๋งŒ <script> ํƒœ๊ทธ ์•ˆ์— ์ฝ”๋“œ๋ฅผ ๋ฆฐํŠธํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ํŽธ์ง‘๊ธฐ ์ง€์›์— ํ•„์š”ํ•˜๋ฉฐ ์ „์ฒด ์›นํŒฉ์„ ํ†ตํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค tslint ๋ช…๋ น์„ ๊ฐœ๋ณ„์ ์œผ๋กœ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์ด ํ›จ์”ฌ ์‰ฝ์Šต๋‹ˆ๋‹ค. ํ๋ฆ„.

@adidahiya ์ด ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ๊ณ„ํš์ด ์žˆ์Šต๋‹ˆ๊นŒ?
๋˜๋Š” eslint ํ”Œ๋Ÿฌ๊ทธ์ธ๊ณผ ๊ฐ™์€ tslint์˜ ํ”Œ๋Ÿฌ๊ทธ์ธ ์‹œ์Šคํ…œ ์ง€์› ๊ณ„ํš์ด ์žˆ์Šต๋‹ˆ๊นŒ?

Webpack์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ .vue ํŒŒ์ผ์ด ์ž‘๋™ํ•˜๊ณ  fork-ts-checker-webpack-plugin ์—์„œ no-consecutive-blank-lines ๋ฌธ์ œ ์—†์ด typechecker ์ˆ˜์ค€์—์„œ linting๋ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—์„œ PR์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(ํ…Œ์ŠคํŠธ ๋ธŒ๋žœ์น˜์—์„œ npm install ๋ฅผ ๋ณด๊ณ  ์ง€๊ธˆ ๋ฐ”๋กœ ์‚ฌ์šฉ ํ•˜์„ธ์š” . PR ์Šค๋ ˆ๋“œ ์ƒ๋‹จ ์ฐธ์กฐ):

๋˜ํ•œ VSCode ํŽธ์ง‘๊ธฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ TSLint Vue ํ™•์žฅ์„ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค.

tslint์—์„œ ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๋Š” pull ์š”์ฒญ์„ ๋งŒ๋“ค๊ณ  "vue" ๋‹จ์ผ ํŒŒ์ผ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๊ตฌ๋ฌธ ๋ถ„์„ํ•  ์ˆ˜ ์žˆ๋Š” ์ฒซ ๋ฒˆ์งธ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

https://github.com/palantir/tslint/pull/3596
https://github.com/Toilal/tslint-plugin-vue

์ด๊ฒƒ์€ ์ง„ํ–‰์ค‘์ธ ์ž‘์—…์ด์ง€๋งŒ ์ž์œ ๋กญ๊ฒŒ ์‹œ๋„ํ•˜์‹ญ์‹œ์˜ค. npm install git+https://https://github.com/Toilal/tslint-plugin-vue ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์ถ”๊ฐ€ํ•˜๊ณ  tslint.json plugins: 'vue' ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ํ™œ์„ฑํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ -p/--project ์˜ต์…˜์—์„œ๋Š” ์ž‘๋™ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ -c/--config ์˜ต์…˜์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์•ˆ๋…•ํ•˜์„ธ์š” ์—ฌ๋Ÿฌ๋ถ„, ํ˜„์žฌ ๋‹ค๋ฅธ ๋Œ€์•ˆ ์†”๋ฃจ์…˜์€ ํŒŒ์ผ ์‹œ์ž‘ ๋ถ€๋ถ„์—์„œ ์Šคํฌ๋ฆฝํŠธ ํƒœ๊ทธ๋ฅผ ์ด๋™ํ•˜๊ณ  ์ด ๊ทœ์น™์„ ๊ณ ๋ คํ•˜์ง€ ์•Š๋„๋ก ๋งˆ์ง€๋ง‰ ์ค„์— /* tslint:disable:no-consecutive-blank-lines */ ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
์•„๋ž˜ ์ด๋ฏธ์ง€๋ฅผ ํ™•์ธํ•˜์„ธ์š”
image

Vue-loader ๋Š” ์‹ค์ œ๋กœ vue ํŒŒ์ผ์—์„œ JavaScript ๋˜๋Š” TypeScript๋ฅผ ์ถ”์ถœํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ vue ํŒŒ์ผ์„ ๊ตฌ๋ฌธ ๋ถ„์„ํ•˜๋Š” ๊ฒƒ์€ webpack์—์„œ ๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ typeCheck๊ฐ€ ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ ํŒŒ์ผ ์ด๋ฆ„์ด ".vue"๋กœ ๋๋‚˜๊ธฐ ๋•Œ๋ฌธ์— TSLint๋Š” ์—ฌ์ „ํžˆ "์ž˜๋ชป๋œ ์†Œ์Šค ํŒŒ์ผ"์„ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค...

๋˜ํ•œ vue-loader๋Š” TypeScript์™€ ๊ด€๋ จ์ด ์—†๋Š” ์ค„์„ ์‚ญ์ œํ•˜์ง€ ์•Š๊ณ  ์˜ฌ๋ฐ”๋ฅธ ์ค„ ๋ฒˆํ˜ธ๋ฅผ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์ง€์šฐ๊ธฐ ๋•Œ๋ฌธ์— vue ํŒŒ์ผ์˜ ์‹œ์ž‘ ๋˜๋Š” ๋ ๋ถ€๋ถ„์— ์—ฐ์†์ ์ธ ๋นˆ ์ค„์„ ํ—ˆ์šฉํ•˜์—ฌ TSLint๊ฐ€ ์—ฐ์†์ ์ธ ๋นˆ ์ค„์— ๋Œ€ํ•ด ๋ถˆํ‰ํ•˜์ง€ ์•Š๋„๋ก ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ €๋Š” ํ˜„์žฌ ์›นํŒฉ ์—†์ด ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ฆฐํ„ฐ๋ฅผ ์ž‘์—… ์ค‘์ž…๋‹ˆ๋‹ค. https://github.com/ajafff/wotan
์ด๊ฒƒ์€ ํ˜„์žฌ ๊ฐœ๋… ์ฆ๋ช…์ด๋ฉฐ TSLint์— ํ†ตํ•ฉ๋˜๊ฑฐ๋‚˜ ํ†ตํ•ฉ๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฒ˜์Œ๋ถ€ํ„ฐ ์™„์ „ํžˆ ๋‹ค์‹œ ์ž‘์„ฑํ–ˆ์œผ๋ฉฐ TSLint์™€ ๊ฑฐ์˜ ํ˜ธํ™˜๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

ํ”„๋กœ์„ธ์„œ(ESLint ํ”„๋กœ์„ธ์„œ์™€ ์œ ์‚ฌ)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŒŒ์ผ์„ ๋ณ€ํ™˜ํ•˜๋Š” ๊ธฐ๋Šฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค. vue ํ”„๋กœ์„ธ์„œ๋Š” SFC์—์„œ TypeScript ์ฝ”๋“œ๋ฅผ ์ถ”์ถœํ•˜๊ณ  linter๋Š” ์ฝ”๋“œ๋งŒ ๋ฆฐํŠธํ•œ ๋‹ค์Œ ํ”„๋กœ์„ธ์„œ๋Š” ์˜ค๋ฅ˜๋ฅผ ์›๋ณธ ํŒŒ์ผ์˜ ์˜ฌ๋ฐ”๋ฅธ ์œ„์น˜์— ๋งคํ•‘ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์œ ํ˜• ๊ฒ€์‚ฌ์—์„œ๋„ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.
์—ฌ๊ธฐ์—์„œ vue ํŒŒ์ผ ๋ฆฐํŠธ์˜ ์ƒ˜ํ”Œ ์ถœ๋ ฅ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
https://github.com/ajafff/wotan/blob/master/baselines/integration/processors/vue/default/hello.vue.lint#L29

๋˜ํ•œ ์ž๋™ ์ˆ˜์ •์„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. ์œ„์™€ ๋™์ผํ•œ ํŒŒ์ผ์ด ์ž๋™์œผ๋กœ ์ˆ˜์ •๋จ: https://github.com/ajafff/wotan/blob/master/baselines/integration/processors/vue/default/hello.vue#L28

https://github.com/ajafff/wotan/issues/32 ์ˆ˜์ •ํ•˜๋ฉด ์‹ค์ œ ์ฝ”๋“œ์—์„œ ์‚ฌ์šฉํ•ด ๋ณผ ์ˆ˜ ์žˆ๋„๋ก ๋ฆด๋ฆฌ์Šค๋ฅผ ๊ฒŒ์‹œํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
vue ํ”„๋กœ์„ธ์„œ๋Š” ๋ฉ”์ธ ํ”„๋กœ์ ํŠธ์˜ ์ผ๋ถ€๊ฐ€ ์•„๋‹ˆ์ง€๋งŒ ํ”„๋กœ์„ธ์„œ์— ๋Œ€ํ•œ ๋ณ„๋„์˜ ํŒจํ‚ค์ง€๋ฅผ ๊ฒŒ์‹œํ•˜๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

๋ช…๋ น์ค„ tslint๊ฐ€ ๋‚ด Vue ํŒŒ์ผ์˜ TS ๋ถ€๋ถ„์„ ๋ฆฐํŠธํ•˜๋„๋ก tsconfig.json ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•„๋Š” ์‚ฌ๋žŒ์ด ์žˆ์Šต๋‹ˆ๊นŒ? VS Code์—์„œ๋Š” ์ž˜ ์ž‘๋™ํ•˜์ง€๋งŒ ๋ช…๋ น์ค„๋„ ์ž‘๋™ํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.

์ƒ๊ฐ๋ณด๋‹ค ์‹œ๊ฐ„์ด ์กฐ๊ธˆ ๋” ๊ฑธ๋ ธ์ง€๋งŒ ์—ฌ๊ธฐ ์žˆ์Šต๋‹ˆ๋‹ค: https://www.npmjs.com/package/@fimbul/wotan

Linter ๋Ÿฐํƒ€์ž„( @fimbul/wotan )์„ ์‚ฌ์šฉํ•˜์—ฌ TSLint ๊ทœ์น™์„ ์‹คํ–‰ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. @fimbul/heimdall ๋งŒ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. https://github.com/fimbullinter/wotan/tree/master/packages/heimdall#readme ์ฐธ์กฐ

Vue ํŒŒ์ผ์„ ๋ฆฐํŠธํ•˜๋ ค๋ฉด @fimbul/ve (์˜คํƒ€ ์—†์Œ, 've'์— 'u' ์—†์Œ): https://github.com/fimbullinter/wotan/tree/master/packages/ve#readme

tslint๊ฐ€ linting vue/html ํŒŒ์ผ์„ ์ง€์›ํ•˜๋Š” ์ผ์ •์ด ์žˆ์Šต๋‹ˆ๊นŒ? @๋šœ์•Œ

์ด๊ฒƒ์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์€ ์ปค๋ฎค๋‹ˆํ‹ฐ์˜ ๋ชซ์ด์ง€๋งŒ VueJS ์ž‘์„ฑ์ž๋Š” vue-cli์˜ ๋งˆ์ง€๋ง‰ ๋ฒ„์ „(ํ˜„์žฌ ์•ŒํŒŒ ๋ฒ„์ „) ๋Œ€์‹  ESLint TypeScript ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์„ ํ˜ธํ•˜๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์ด ๊ธฐ๋Šฅ์€ ์ด๋ฏธ [email protected] ๊ตฌํ˜„๋œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์šด์˜:

$ npm install -g @vue/cli
$ vue create project-name

๊ทธ๋ฆฌ๊ณ  TSLint๋ฅผ ๋ฆฐํ„ฐ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ์Šค๋ ˆ๋“œ์˜ ๋ชจ๋“  ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ๋” ์ข‹์€ ์†Œ์‹์„ ๊ฐ€์ง€๊ณ  ๋Œ์•„์™”์Šต๋‹ˆ๋‹ค.

์ตœ์‹  ๋ฒ„์ „์˜ wotan ์€ tslint.json ์— ๋”ฐ๋ผ TSLint ๊ทœ์น™์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ํ”„๋กœ์„ธ์„œ์— ๋Œ€ํ•œ ์ง€์›์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, ๊ตฌ์„ฑ์„ ๋ณ€๊ฒฝํ•  ํ•„์š”๊ฐ€ ์—†์œผ๋ฉฐ ์ง€๊ธˆ Vue ํŒŒ์ผ ๋ฆฐํŠธ๋ฅผ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  1. ์„ค์น˜
    sh yarn add -D @fimbul/wotan @fimbul/ve @fimbul/valtyr # or npm install --save-dev @fimbul/wotan @fimbul/ve @fimbul/valtyr
  2. ๊ตฌ์„ฑ
    ํ”„๋กœ์ ํŠธ์˜ ๋ฃจํŠธ ๋””๋ ‰ํ„ฐ๋ฆฌ์— ์ƒˆ ํŒŒ์ผ .fimbullinter.yaml ์„ ์ถ”๊ฐ€ํ•˜๊ณ  ๋‹ค์Œ ์ฝ˜ํ…์ธ ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
    yaml modules: "@fimbul/valtyr" valtyr: overrides: - files: "*.vue" processor: "@fimbul/ve"
  3. ์šด์˜
    ์•„๋ž˜์˜ ์˜ˆ๋ฅผ ๋ณด๊ณ  ์ž์‹ ์˜ ์šฉ๋„์— ๋งž๊ฒŒ ์กฐ์ •ํ•˜์‹ญ์‹œ์˜ค. ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ CLI ์ธ์ˆ˜์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ ๋ฌธ์„œ๋ฅผ ์ฝ์œผ์‹ญ์‹œ์˜ค.
    sh wotan # finds tsconfig.json and lints the whole project with type information according to your tslint.json wotan 'src/**/*.vue' -f verbose # lint all Vue files, use TSLint's verbose formatter wotan -p tsconfig.json -c tslint.json --fix # lint the whole project with tslint.json and fix failures
  4. ์ถ”๊ฐ€ ์ฝ๊ธฐ
    Wotan - CLI ๋ฐ ๊ตฌ์„ฑ: https://github.com/fimbullinter/wotan/tree/master/packages/wotan#readme
    Valtรฝr - TSLint ๊ทœ์น™ ๋ฐ ํฌ๋งทํ„ฐ์šฉ ํ”Œ๋Ÿฌ๊ทธ์ธ - "TSLint๋ณด๋‹ค ๋‚˜์€ TSLint ๋Ÿฐํƒ€์ž„": https://github.com/fimbullinter/wotan/tree/master/packages/valtyr#readme
    Vรฉ - Vue ๋‹จ์ผ ํŒŒ์ผ ๊ตฌ์„ฑ ์š”์†Œ์šฉ ํ”„๋กœ์„ธ์„œ: https://github.com/fimbullinter/wotan/tree/master/packages/ve#readme
    Fimbullinter - ์ง€๊ธˆ ์ด ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•ด์•ผ ํ•˜๋Š” ์ด์œ : https://github.com/fimbullinter/wotan#readme
  5. ์ข‹์•„์š”, ๊ณต์œ  ๋ฐ ๊ตฌ๋…:๋ณ„:

@romandragan

vue-loader๋ฅผ ๊ตฌ์„ฑํ•˜์—ฌ Vue ๋‹จ์ผ ํŒŒ์ผ ๊ตฌ์„ฑ ์š”์†Œ์™€ ํ•จ๊ป˜ TSLint๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์†”๋ฃจ์…˜์— ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด๊ฒƒ์€ Webpack ํŠน์ • ์†”๋ฃจ์…˜์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. ๋นŒ๋“œ ๋„๊ตฌ์— ์˜์กดํ•˜์ง€ ์•Š๋Š” ์ผ๋ฐ˜ ์†”๋ฃจ์…˜์ด ์žˆ์œผ๋ฉด Fusebox (๋˜๋Š” ๋‹ค๋ฅธ ๋นŒ๋“œ ๋„๊ตฌ)๋กœ ์ปดํŒŒ์ผ๋œ Vue ํŒŒ์ผ์„ tslintํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์›นํŒฉ์„ ์œ„ํ•œ ๋˜ ๋‹ค๋ฅธ ์†”๋ฃจ์…˜์ž…๋‹ˆ๋‹ค.

vue-loader์—์„œ ์ œ๊ณตํ•˜๋Š” "ํŒŒ์ผ"์—์„œ ๋ชจ๋“  ๊ณต๋ฐฑ์„ ํŠธ๋ฆฌ๋ฐํ•œ ๋‹ค์Œ ๋ฆฐํ„ฐ์— ๊ณต๋ฐฑ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฐ„๋‹จํ•œ ๋กœ๋”๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

webpack.config.js:

test:/\.vue$/, loader: 'vue-loader', options: { loaders: { 'ts': [ 'vue-ts-loader', 'tslint-loader', path.resolve('./path/to/remove-whitespace-ts-loader.js') ],....

๊ทธ๋Ÿฐ ๋‹ค์Œ remove-whitespace-ts-loader:
/* MIT License http://www.opensource.org/licenses/mit-license.php Author Szymon Sasin */ module.exports = function(source) { let result = source.replace(/^\s+|\s+$/g, '') result += '\r\n' return result; }
_์ฝ”๋“œ ํ˜•์‹์„ ๊ฐœ์„ ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ๋ชจ๋“  ๋„์›€์„ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค_

๊ทธ๊ฒƒ์€ ์ž˜ ์ž‘๋™ํ•˜๊ณ  ์†Œ์Šค๋ฅผ ์ˆ˜์ •ํ•˜์ง€ ์•Š๋„๋ก ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค, ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค

๊ทธ๋ƒฅ ๊ฐ™์€ ์ž…์žฅ์ธ๋ฐ, PR๋กœ ํ•˜์ง€ ์•Š์œผ๋ฉด ํ•ด๊ฒฐ๋˜์ง€ ์•Š๋Š” ๊ฑด๊ฐ€์š”?

no-consecutive-blank-lines ๊ฐ€ ์ด ์„ค์ •์—์„œ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ ์™ธ์—(์‚ฌ์šฉ์ž ์ง€์ • ๋กœ๋”๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•จ), ์ตœ๊ทผ webpack ๋ฐ vue-loader ๋ฒ„์ „์—์„œ๋„ typeCheck: true tslint ์ด ํ™•์žฅ์— ๋งŒ์กฑํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— typeCheck: true *.vue ํŒŒ์ผ์— ๋Œ€ํ•ด ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค( appendTsSuffixTo ). ์ด ๋ฐ๋ชจ ํ”„๋กœ์ ํŠธ ๋Š” ๊ฐ€์ง€๊ณ  ๋†€ ์ˆ˜ ์žˆ๋Š” ๊ฐ„๋‹จํ•œ ์˜ˆ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

ํ‘œ์‹œ

์—ฌ๊ธฐ์— ๋” ๋งŽ์€ ์†Œ์‹์ด ์žˆ์Šต๋‹ˆ๊นŒ? ๐Ÿค™

๋ˆ„๊ตฐ๊ฐ€ cli๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ .vue ํŒŒ์ผ์„ lintํ•˜๋ ค๋Š” ๊ฒฝ์šฐ vue-tslint๋ฅผ ์‹œ๋„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์™„๋ฒฝํ•˜์ง€๋Š” ์•Š์ง€๋งŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค ...

@romandragan ์˜ ์†”๋ฃจ์…˜์€ ์ด์ œ ๋ชจ๋ฒ” ์‚ฌ๋ก€๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค(์ด์ „์—๋Š” \.ts$ ๊ทœ์น™์˜ use ์— tslint-loader ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”. ์ด์ œ vue-loader ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ํ…œํ”Œ๋ฆฟ ํŒŒ์ผ์„ ์ฒ˜๋ฆฌํ•˜์—ฌ ํ•ด๋‹น ํ™•์žฅ์— ๋Œ€ํ•œ Webpack ๊ตฌ์„ฑ์— ๋‹ค๋ฅธ ์„น์…˜์„ ์œ„์ž„ํ•ฉ๋‹ˆ๋‹ค. ์˜ ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ตฌ์„ฑ ์š”์†Œ vue-loader ๊ทœ์น™ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค \.ts$ ํ•˜๊ณ  ์ŠคํŠธ๋ฆฌ๋ฐ <script lang="ts"> ์ด ๊ทœ์น™์„ ํ†ตํ•ด ๋ธ”๋ก์„.

Vue CLI๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ์ถ”๊ฐ€

config.module.rule('ts')
  .use('tslint-loader')
  .loader('tslint-loader');

vue.config.js ์˜ chainWebpack ์˜ต์…˜์— ๋Œ€ํ•œ ๋ช…์„ธ์„œ ์˜ˆ๋ฅผ ๋“ค์–ด,

vue.config.js

module.exports = {
  chainWebpack: (config) => {
    config.devtool('source-map');
    config.module.rule('ts')
      .use('tslint-loader')
      .loader('tslint-loader');
  },
  pluginOptions: {
    apollo: {
      enableMocks: true,
      enableEngine: true
    }
  }
}

์ด๊ฒƒ์€ ๋‚˜๋ฅผ ์œ„ํ•ด ์ผํ–ˆ์Šต๋‹ˆ๋‹ค.

const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')

module.exports = (baseConfig, env, defaultConfig) => {
  defaultConfig.resolve.extensions.push('.ts', '.tsx', '.vue', '.css', '.less', '.scss', '.sass', '.html')

  defaultConfig.module.rules.push( {
    test: /\.ts$/,
    exclude: /(node_modules)/,
    use: [
      {
        loader: 'ts-loader',
        options: {
          appendTsSuffixTo: [/\.vue$/]
          // transpileOnly: true,
        }
      },
      {
        loader: 'tslint-loader',
        options: {
          configFile: 'tslint.json',
          emitErrors: true,
        }
      }
    ]
  })

  defaultConfig.module.rules.push({ test: /\.less$/, loaders: [ 'style-loader', 'css-loader', 'less-loader' ] })
  defaultConfig.module.rules.push({ test: /\.scss$/, loaders: [ 'style-loader', 'css-loader', 'sass-loader' ] })
  defaultConfig.module.rules.push({ test: /\.styl$/, loader: 'style-loader!css-loader!stylus-loader' })

  defaultConfig.plugins.push(new ForkTsCheckerWebpackPlugin())

  return defaultConfig
}

#4379์— ๋”ฐ๋ผ Vue ๊ด€๋ จ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์€ TSLint ์ฝ”์–ด์— ์ฐฉ๋ฅ™ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์— ํ† ๋ก ์ด๋‚˜ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ์ด ์—†๊ณ  ESLint(#4534)์— ์ฐฌ์„ฑํ•˜์—ฌ TSLint๊ฐ€ ๊ณง ์‚ฌ์šฉ ์ค‘๋‹จ๋œ๋‹ค๋Š” ์ ์„ ๊ฐ์•ˆํ•  ๋•Œ ์ด ๋ฌธ์ œ์™€ ๊ด€๋ จ Vue ๊ด€๋ จ ๋ณด๊ณ ์„œ๋ฅผ ์ •๋ฆฌ ๋ชฉ์ ์œผ๋กœ ์ข…๋ฃŒํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

์ด๊ฒƒ์ด ์—ฌ์ „ํžˆ typescript-eslint ์—์„œ ๋ฌธ์ œ๋ผ๋ฉด ๊ฑฐ๊ธฐ์— ๋ฌธ์ œ๋ฅผ ์ œ์ถœํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ํ–‰์šด์„ ๋น•๋‹ˆ๋‹ค!

./node_modules/.bin/wotan 'src/ */ .vue' -f ์ƒ์„ธ

์ถฉ๋Œ:

Error: ENOENT: no such file or directory, open '/home/andrew/PycharmProjects/djangochat/fe/src/components/App.vue.ts'
    at Object.openSync (fs.js:451:3)
    at detectEncoding (/home/andrew/PycharmProjects/djangochat/fe/node_modules/tslint/lib/rules/encodingRule.js:67:17)
    at walk (/home/andrew/PycharmProjects/djangochat/fe/node_modules/tslint/lib/rules/encodingRule.js:49:20)
    at Rule.AbstractRule.applyWithFunction (/home/andrew/PycharmProjects/djangochat/fe/node_modules/tslint/lib/language/rule/abstractRule.js:39:9)
    at Rule.apply (/home/andrew/PycharmProjects/djangochat/fe/node_modules/tslint/lib/rules/encodingRule.js:33:21)
    at R.apply (/home/andrew/PycharmProjects/djangochat/fe/node_modules/@fimbul/bifrost/src/index.js:30:40)
    at Linter.applyRules (/home/andrew/PycharmProjects/djangochat/fe/node_modules/@fimbul/wotan/src/linter.js:209:31)
    at Linter.getFindings (/home/andrew/PycharmProjects/djangochat/fe/node_modules/@fimbul/wotan/src/linter.js:125:25)
    at Runner.lintFiles (/home/andrew/PycharmProjects/djangochat/fe/node_modules/@fimbul/wotan/src/runner.js:159:43)
    at lintFiles.next (<anonymous>) {
  errno: -2,
  syscall: 'open',
  code: 'ENOENT',
  path: '/home/andrew/PycharmProjects/djangochat/fe/src/components/App.vue.ts'
}

@akoidan tslint๊ฐ€ ์•„๋‹Œ wotan ํ”„๋กœ์ ํŠธ์—์„œ ๊ทธ๋Ÿฐ ๋ฌธ์ œ๋ฅผ ์ œ๊ธฐํ•˜์‹ญ์‹œ์˜ค.

์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰