eslint-plugin-vue: Wrong detect of `Parsing error: invalid-first-character-of-tag-name` in expression.

Tell us about your environment

  • ESLint Version:

4.16.0

  • eslint-plugin-vue Version:

4.2.2

  • Node Version:

v8.1.3

Please show your full configuration:

const isDev = process.env.NODE_ENV === 'development';

// http://eslint.org/docs/user-guide/configuring
module.exports = {
  root: true,
  parserOptions: {
    parser: 'babel-eslint',
    sourceType: 'module'
  },
  env: {
    browser: true,
  },
  extends: [
    'eslint:recommended',
    'plugin:vue/recommended', // or 'plugin:vue/base'
    'airbnb-base',
  ],
  // required to lint *.vue files
  plugins: [
    'vue',
  ],
  // check if imports actually resolve
  'settings': {
    'import/resolver': {
      'webpack': {
        'config': 'build/webpack.base.conf.js'
      }
    },
  },
  // add your custom rules here
  'rules': {
    // eslint rules
    'camelcase': ['error', {
      'properties': 'always'
    }],
    'function-paren-newline': ['error', 'consistent'],
    'id-match': ['error', '^(\\${0,1}[a-z]+[a-zA-Z_]*||[A-Z_0-9]+||[pk][12])$', {
      'onlyDeclarations': true,
    }],
    'max-len': ['error', {
      'code': 140,
      'ignoreTrailingComments': true,
      'ignoreStrings': true,
      'ignoreTemplateLiterals': true,
      'ignoreUrls': true,
      'ignoreComments': true
    }],
    'no-underscore-dangle': 0,
    'no-return-assign': 0,
    'object-curly-newline': ['error', {
      'consistent': true
    }],
    'one-var': ['error', {
      'initialized': 'never',
    }],
    'one-var-declaration-per-line': ['error', 'initializations'],
    'prefer-destructuring': 0,
    // allow debugger during development
    'no-debugger': isDev ? 0 : 2,
    'no-console': isDev ? 0 : 1,
    'no-unused-vars': isDev ? 0 : 1,

    // don't require .vue extension when importing
    'import/extensions': ['error', 'always', {
      'js': 'never',
      'vue': 'never'
    }],
    // allow optionalDependencies
    'import/no-extraneous-dependencies': ['error', {
      'optionalDependencies': ['test/unit/index.js']
    }],
    // allow single export
    'import/prefer-default-export': 'off',

    // vue lint configs
    'vue/attribute-hyphenation': ['error', 'always'],
    'vue/html-end-tags': 'error',
    'vue/html-indent': ['error', 2, {
      'attribute': 1,
      'closeBracket': 0,
      'ignores': []
    }],
    'vue/html-quotes': ['error', 'double'],
    'vue/html-self-closing': ['error', {
      'html': {
        'normal': 'never',
        'void': 'never',
        'component': 'never'
      },
      'svg': 'always',
      'math': 'always',
    }],
    'vue/max-attributes-per-line': [2, {
      'singleline': 10,
      'multiline': {
        'max': 2,
        'allowFirstLine': false
      },
    }],
    'vue/mustache-interpolation-spacing': ['error', 'always'],
    'vue/name-property-casing': ['error', 'kebab-case'],
    'vue/no-async-in-computed-properties': 'error',
    'vue/no-confusing-v-for-v-if': 'error',
    'vue/no-dupe-keys': 'error',
    'vue/no-duplicate-attributes': ['error', {
      allowCoexistClass:  true,
      allowCoexistStyle: true,
    }],
    'vue/no-multi-spaces': 'error',
    'vue/no-parsing-error': 'error',
    'vue/no-reserved-keys': ['error', {
      'reserved': ['$el', '$nextTick', '$route', '$router', 'asyncData'],
      'groups': [],
    }],
    'vue/no-shared-component-data': 'error',
    'vue/no-side-effects-in-computed-properties': 'error',
    'vue/no-template-key': 'error',
    'vue/no-textarea-mustache': 'error',
    // 'vue/order-in-components': ['error', {
    //   'order': [
    //     ['name', 'delimiters', 'functional', 'model'],
    //     ['components', 'directives', 'filters'],
    //     ['parent', 'mixins', 'extends', 'provide', 'inject'],
    //     'el',
    //     'template',
    //     'props',
    //     'propsData',
    //     'data',
    //     'computed',
    //     'watch',
    //     'asyncData',
    //     'onWechatReady',
    //     'LIFECYCLE_HOOKS',
    //     'methods',
    //     'render',
    //     'renderError'
    //   ],
    // }],
    'vue/require-component-is': 'error',
    'vue/require-default-prop': 'error',
    'vue/require-prop-types': 'error',
    'vue/require-render-return': 'error',
    'vue/require-v-for-key': 'error',
    'vue/require-valid-default-prop': 'error',
    'vue/return-in-computed-property': 'error',
    'vue/this-in-template': ['error', 'never'],
    'vue/v-bind-style': ['error', 'shorthand'],
    'vue/v-on-style': ['error', 'shorthand'],
    'vue/valid-template-root': 'error',
    'vue/valid-v-bind': 'error',
    'vue/valid-v-cloak': 'error',
    'vue/valid-v-else-if': 'error',
    'vue/valid-v-else': 'error',
    'vue/valid-v-for': 'error',
    'vue/valid-v-html': 'error',
    'vue/valid-v-if': 'error',
    'vue/valid-v-model': 'error',
    'vue/valid-v-on': 'error',
    'vue/valid-v-once': 'error',
    'vue/valid-v-pre': 'error',
    'vue/valid-v-show': 'error',
    'vue/valid-v-text': 'error',
  }
}

What did you do? Please include the actual source code causing the issue.

            <div class="pull-down-touching-tip__text">
              {{ scope.distance < fireDistance ? '继续下拉刷新' : '松手刷新' }}
            </div>

What did you expect to happen?

No error.

What actually happened? Please include the actual, raw output from ESLint. image

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 8
  • Comments: 45 (18 by maintainers)

Most upvoted comments

This is still a thing, and it is absurd.

The contents of {{ }} is executed as a JavaScript expression. It isn’t HTML by any stretch of the imagination. Having to escape html reserved characters is very unnatural in what is essentially a script.

Let JavaScript expressions be expressions. Fix this bug.

It’s still in HTML.

By that logic, anything inside of a <script> tag is HTML.

Interpolated JS should absolutely not be treated as HTML. Being able to use > but having to use &lt; instead of < is absolutely ridiculous, I can’t believe that’s an acceptable solution.

Oh? {{ scope.distance &lt; fireDistance ? 'Pull to refresh' : 'Release to refresh' }} works fine in my environment.

No this is NOT html, it’s in {{}}, which means it is a inline JavaScript expression.

I re-thought about this, I decided that I don’t add that option.

It messes the code easily by removing a space. Inside of mustaches is still HTML.

<!-- Oops, this is not a mustache, there is `<fireDistance>` tag. -->
{{ scope.distance <fireDistance ? 'Pull to refresh' : 'Release to refresh' }}

I’d like to recommend to use &lt; (note it requires semicolon) for <.

Thank you.

Thank you for the report.

This is a correct error as according to HTML spec:

To make valid HTML, you should use &lt; instead of the <. Or you can ignore the error by the option of vue/no-parsing-error rule: https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/no-parsing-error.md#wrench-options

I completely agree with @Smilebags - Vue template is something more than a pure HTML and the linter should obey this fact. Everything inside a mustache and the mustache itself is and should be practically and theoretically invisible for the HTML parser.

It’s still in HTML.

I think we can all agree there are no HTML tags in {{ a < b ? 1 : 2 }}. So it follows that seeing an “invalid-first-character-of-tag-name” is a mistake.

Either the warning is valid or it isn’t. And seeing how there aren’t any tags in that statement, the warning is not valid.

I think we all understand the situation that leads to the warning being generated today, but the why doesn’t really change the should.

By that logic, anything inside of a <script> tag is HTML.

Exactly. So the following code is syntax error since the script ends at the first </script>. HTML spec allows < in script elements.

<script>
    document.body.innerHTML += "</script>"
</script>

As same, {{ a<b ? 1 : 2}} is syntax error because <b> tag exists there. This is just a fact. I cannot change it.

@mysticatea Please disable as default, its really annoying 😦

For reference, add this to your eslintrc.js (eslint config) file to remove notice:

rules: {
    'vue/no-parsing-error': [2, {
      "invalid-first-character-of-tag-name": false
    }]
  }

No. the inside of {{ }} is HTML in Vue.js 2.x. That’s the reason that I rewrote HTML parser for Vue.js 3 from scratch.

This is just a fact. I don’t think the reopen makes sense.

In Vue.js 3 support, this behavior will change, I think. Because the inside of {{ }} in Vue.js 3 is not HTML.

Quick hotfix to this with error: image no error: image

the rule invalid-first-character-of-tag-name should not be parsed inside mustache tags, inside it is javascript not html, dont matter what is your justification for this, its a compiler warning mustache tags are not suposed to be rendered in browser, so it doesnt make sense

Same problem, use >instead of <

<a v-for="item in items" :key="item._id">
  {{ (item.name.length > 6) ? item.name.slice(0,6) + '...' : item.name }}
</a>

image

Not quite what I’m talking about.

https://jsfiddle.net/krvftv74/2/

It’s kind of funny that my example is throwing HTML linter errors, but my point is the Vue linter should know better.

The expression {{ scope.distance < fireDistance ? 'Pull to refresh' : 'Release to refresh' }} in template, will be rendered into Pull to refresh or Release to refresh due to scope.distance and fireDistance. There is no spec in the result. I can’t understand why lint report an error here. 😕

All other arguments about syntax aside, if JavaScript expressions (be them ternary or otherwise) are supported and encouraged by VueJS, it should probably not throw an error in a VueJS linter. alt; is not a JavaScript operator and anyone’s opinion to the contrary is diverging standard.

LOL, still a problem

No. the inside of {{ }} is HTML in Vue.js 2.x. That’s the reason that I rewrote HTML parser for Vue.js 3 from scratch. This is just a fact. I don’t think the reopen makes sense. In` Vue.js 3 support, this behavior will change, I think. Because the inside of {{ }} in Vue.js 3 is not HTML.

still have this issue with Vue3 in 2021. I think this issue should be open.

@bumprat Are you sure your v-text will be compiled as expression rather than plain text ?

check v-text documentation

v-text Updates the element’s textContent.

Internally, {{ Mustache }} interpolations are also compiled as a v-text directive on a textNode.

use v-text ( or v-html )

<!-- will pass -->
<div v-text="a < b ? 'c' : 'd'"></div>
<!-- will fail -->
<div>{{ a < b ? 'c' : 'd' }}</div>

Go in packages.json and add this code rules: { "vue/no-parsing-error": [2, { "invalid-first-character-of-tag-name": false }] }

still a problem

I know that HTML syntax is very tolerant. Browsers/Parsers work fine even if HTML parse error. But HTML parse errors still exist, so the rule reports it and has the options which are to ignore HTML parse errors.

OK, I opened an issue to add option which ignores HTML syntax errors in mustaches in vue-eslint-parser repo.