Tslint: Support linting vue/html file

Created on 22 Jan 2017  ·  35Comments  ·  Source: palantir/tslint

Feature Request

  • __TSLint version__: 4.3.1
  • __TypeScript version__: 2.1.5
  • __Running TSLint via__: Node.js API

TypeScript code being linted

<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>

with tslint.json configuration:

{
  "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"
    ]
  }
}

Actual behavior

Tries to lint the whole file and fails.

Expected behavior

check for code inside script tags only, similar to eslint.

Aged Away Feature Request

Most helpful comment

Some more news here? 🤙

All 35 comments

@nrip-monotype, you can use TSLint with Vue single file components by configuring vue-loader. For example in webpack 2 I'm using it like this (take a look at the loaders option of the 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
                }
            },
            {
                test: /\.vue$/,
                loader: 'vue-loader',
                options: {
                    loaders: {
                        ts: 'ts-loader!tslint-loader'
                    }
                }
            },
        }
    }
}

@romandragan, your suggestion works, but seems to have issues with no-consecutive-blank-lines. It seems as if the markup elements are replaced by empty lines, for example:

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

Is seen by tslint as:

    return {};




If i set "no-consecutive-blank-lines": [true, 3] it fails, but "no-consecutive-blank-lines": [true, 4] succeeds... Any idea how got get around this? (short of another plugin that strips leading and trailing whitespace...)

@lucastheisen, same issue for me 😞 Trying to find a solution...

@romandragan's setup worked for me, but something to note is that the typeCheck flag for tslint-loader doesn't work in vue-loader. You can still use it normally outside of 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.
                    }
                }
            },
        }
    }
}

Trying to use type-checked rules in vue-loader yields an error like this:

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.

I'm also getting the same no-consecutive-blank-lines false positive as @lucastheisen and @romandragan.

I'm getting this ERROR in Entry module not found: Error: Can't resolve 'ts-loader!tslint-loader' when using this config:

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

@ARomancev, what version of webpack are you using? Do you have ts-loader and tslint-loader npm modules installed?

@romandragan, here is the npm config im using:
```
"tslint": "^5.1.0",
"ts-loader": "^2.0.3",
"webpack": "^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 and tslint-loader both installed and work fine since i get correct lint from *.ts files.

As a workaround we can separate TypeScript logic from .vue file and remove tslint-loader from vue loaders. Then, no-consecutive-blank-lines disappears.

@romandragan this works, but it would be nice to lint code inside <script> tag as well: this is needed for editor support and it's much easier to run tslint command separately, rather then through whole webpack flow.

@adidahiya any plan for this feature?
or any plan for support plugin system in tslint like eslint plugins?

If you are using Webpack, I've got .vue files working and linting at typechecker level with no no-consecutive-blank-lines issue in fork-ts-checker-webpack-plugin. You can see the PR here (and npm install from a test branch and use it right now, see top of PR thread): https://github.com/Realytics/fork-ts-checker-webpack-plugin/pull/77

Also, if you are using VSCode editor, check out the TSLint Vue extension.

I have created a pull request that implements a plugin feature in tslint, and wrote the first plugin able to parse "vue" single file components.

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

This is a work in progress, but feel free to try. You can add the plugin with npm install git+https://https://github.com/Toilal/tslint-plugin-vue and enable it by adding plugins: 'vue' into tslint.json. Currently it doesn't work with -p/--project option, you have to use -c/--config option.

Hi guys, another alternative solution, for now, is moving the script tag at the beginning of the file and add the /* tslint:disable:no-consecutive-blank-lines */ in the last line to don't take into account this rule.
Check the image below
image

Vue-loader can actually extract JavaScript or TypeScript from vue files, so parsing vue files is not a problem for webpack. However, TSLint still throws "Invalid source file" because file names are ended with ".vue" if typeCheck is enabled...

In addition, we can make TSLint stop complaining consecutive blank lines by allowing consective blank lines at the beginning or the end of vue files, because vue-loader does not delete lines irrelevant to TypeScript but clear them instead to maintain correct line numbers.

I'm currently working on a linter that could solve these problems without the need for webpack: https://github.com/ajafff/wotan
This is currently a proof of concept and may or may not be integrated into TSLint. It's a complete rewrite from scratch and pretty much incompatible with TSLint.

It has the ability to transform files using a processor (similar to ESLint processors). A vue processor extracts the TypeScript code from the SFC, the linter only lints the code, then the processor maps the errors to the correct location in the original file. This even works with type checking.
You can see a sample output of linting a vue file here:
https://github.com/ajafff/wotan/blob/master/baselines/integration/processors/vue/default/hello.vue.lint#L29

It also allows automatic fixing. Same file as above automatically fixed: https://github.com/ajafff/wotan/blob/master/baselines/integration/processors/vue/default/hello.vue#L28

Once I have fixed https://github.com/ajafff/wotan/issues/32 I will publish a release so you can try it on your real world code.
Note that the vue processor is not part of the main project but I'll make sure I publish a separate package for the processor.

Does anyone know how I would set up tsconfig.json so that command-line tslint will lint the TS portion of my Vue files? It works fine in VS Code, but I'd like the command line to work as well.

I took a bit longer than I thought, but here it is: https://www.npmjs.com/package/@fimbul/wotan

You can even use the linter runtime (@fimbul/wotan) to execute TSLint rules. You only need @fimbul/heimdall, see https://github.com/fimbullinter/wotan/tree/master/packages/heimdall#readme

To lint Vue files, use @fimbul/ve (no typo, there is no 'u' in 've'): https://github.com/fimbullinter/wotan/tree/master/packages/ve#readme

Is there a schedule when will tslint support linting vue/html file? @Toilal

It's up to the community to implement this, but VueJS author seems to go in favor of ESLint TypeScript plugins instead in last version of vue-cli (currently in alpha).

This feature seems to be already implemented in [email protected]. Run:

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

And there will be a choice of using TSLint as the linter.

I'm back with even greater news for everyone on this thread:

The latest version of wotan can execute TSLint rules according to your tslint.json and adds support for processors. That means you don't have to change your config and can start linting your Vue files now:

  1. Install
    sh yarn add -D @fimbul/wotan @fimbul/ve @fimbul/valtyr # or npm install --save-dev @fimbul/wotan @fimbul/ve @fimbul/valtyr
  2. Configure
    Add a new file .fimbullinter.yaml in the root directory of your project and add the following content:
    yaml modules: "@fimbul/valtyr" valtyr: overrides: - files: "*.vue" processor: "@fimbul/ve"
  3. Run
    See examples below and adapt for your own use. Read the docs for more information about available CLI arguments.
    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. Further Reading
    Wotan - CLI and configuration: https://github.com/fimbullinter/wotan/tree/master/packages/wotan#readme
    Valtýr - plugin for TSLint rules and formatters - "the TSLint runtime that's better than TSLint": https://github.com/fimbullinter/wotan/tree/master/packages/valtyr#readme
    Vé - processor for Vue single file components: https://github.com/fimbullinter/wotan/tree/master/packages/ve#readme
    Fimbullinter - why you should start using this project now: https://github.com/fimbullinter/wotan#readme
  5. Like, Share and Subscribe :star:

@romandragan

you can use TSLint with Vue single file components by configuring vue-loader.

Thanks for the solution. But it's just a Webpack specific solution. It would be great if there was a generic solution that's not dependant on a build tool so that we could tslint Vue files compiled with Fusebox (or any other build tool).

Another solution for webpack.

I have created a simple loader which trims all whitespaces from a "file" provided by vue-loader and then add white space for a linter.

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') ],....

and then 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; }
_Any help to how to improve the code formatting welcomed_

It works fine and permits not to modify sources, thanks

Just so I am on the same page, is this not going to be fixed unless it is done as a PR?

Besides no-consecutive-blank-lines not working in this setup (which should be work-aroundable with a custom loader), I'm just noting that, even with recent webpack and vue-loader versions, I can't get the typeCheck: true working for *.vue files because tslint is unhappy with the extension (even with appendTsSuffixTo). This demo project gives a simple example to play around with.

mark

Some more news here? 🤙

If someone want to use cli to lint your .vue file, you can try vue-tslint. It's not perfect, but it works...

Note that @romandragan 's solution is now not best-practice (not sure if it was so previously). Instead, add tslint-loader to your use in \.ts$ rules like you would for usual typescript files; this is now vue-loader handles template files in general, delegating the different sections to your Webpack configuration for those respective extensions. The plugin component of vue-loader will extract the rules for \.ts$ and stream <script lang="ts"> blocks through these rules.

For those using Vue CLI, add a

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

statement to your chainWebpack option in your vue.config.js. For example,

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
    }
  }
}

This worked for me

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
}

Per #4379, a Vue-specific workaround isn't going to be able to land in TSLint core:

Given the lack of discussion or concrete proposal here and the upcoming deprecation of TSLint in favor of ESLint (#4534), I'm going to go ahead and close this issue and related Vue-specific reports for housekeeping purposes.

If this is still an issue for you in typescript-eslint, I'd recommend filing an issue there. Best of luck!

./node_modules/.bin/wotan 'src/*/.vue' -f verbose

Crashes with:

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 please bring up issues like that with the wotan project, not tslint

Was this page helpful?
0 / 5 - 0 ratings