Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrong detect of Parsing error: invalid-first-character-of-tag-name in expression. #370

Closed
tinymins opened this issue Jan 31, 2018 · 45 comments
Labels

Comments

@tinymins
Copy link
Contributor

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

@mysticatea
Copy link
Member

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

@tinymins
Copy link
Contributor Author

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

@mysticatea
Copy link
Member

It's still in HTML.

@tinymins
Copy link
Contributor Author

Thanks for reply. So what should I do to compare two numbers and use conditional operator correctly in vue template?

@tinymins
Copy link
Contributor Author

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

@mysticatea
Copy link
Member

mysticatea commented Jan 31, 2018

To be valid HTML, {{ scope.distance &lt; fireDistance ? 'Pull to refresh' : 'Release to refresh' }}.

But HTML spec allows {{ scope.distance < fireDistance ? 'Pull to refresh' : 'Release to refresh' }} as well due to well-defined error recovery logic. It's the reason why vue/no-parsing-error rule has the options to ignore HTML parse errors.

@tinymins
Copy link
Contributor Author

Actually, {{ scope.distance &lt; fireDistance ? 'Pull to refresh' : 'Release to refresh' }} can not pass the compiler.
image

It is really a great feature to detect specs in real HTML, and I need this very much. Some members in my team are always careless about use &lt. They think 'OH IT WORKS!', which makes me feel very annoying. That's why I do not want to disable it globally.

@mysticatea
Copy link
Member

mysticatea commented Jan 31, 2018

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

@tinymins
Copy link
Contributor Author

Full file index.vue:

<template>
  <div>A is {{ a > b ? 'larger than' : 'smaller or equals with' }} B</div>
</template>

<script>
export default {
  data() {
    return {
      a: Math.random(),
      b: Math.random(),
    };
  },
};
</script>

You can try this code.

@tinymins
Copy link
Contributor Author

image
Here's the expected result.

@mysticatea
Copy link
Member

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.

@mysticatea
Copy link
Member

mysticatea commented Feb 2, 2018

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.

@tinymins
Copy link
Contributor Author

tinymins commented Feb 2, 2018

Thanks, I'll use &lt; inside the mustache.

@Nfinished
Copy link

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.

@mysticatea
Copy link
Member

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.

@Nfinished
Copy link

So wouldn't it follow that {{ ends at the first }}?

@mysticatea
Copy link
Member

@Nfinished
Copy link

Nfinished commented Feb 28, 2018

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.

@steffanhalv
Copy link

steffanhalv commented Aug 2, 2018

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

@butonly
Copy link

butonly commented Sep 29, 2018

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

@Smilebags
Copy link

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.

@tmcdos
Copy link

tmcdos commented Feb 19, 2019

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.

@thejcannon
Copy link

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.

@fungiboletus
Copy link

Is the rule invalid-first-character-of-tag-name useful ? Perhaps it can be removed if it's too challenging to fix it.

@hoomersinpsom
Copy link

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

@petsgre
Copy link

petsgre commented Mar 24, 2020

when can fix it ?

@diego-lipinski-de-castro

LOL, still a problem

@emilio-ab
Copy link

still a problem

@helgard-mez
Copy link

Can we please get someone to re-evaluate this ticket? Based on the responses from @mysticatea it's quite evident that they either have a fundamental misunderstanding of how string interpolation works in Vue, or the purpose behind string interpolation in Vue.

@tmcdos
Copy link

tmcdos commented Apr 24, 2020

Indeed, the problem is in vue-eslint-parser and not in eslint-plugin-vue. Here is the patch to fix the problem
vue-eslint-parser v7.0.0.patch.txt

@ota-meshi
Copy link
Member

You can use the following options:

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

You can check with DEMO.

@quannt
Copy link

quannt commented May 15, 2020

This is broken if you are using @babel/plugin-proposal-optional-chaining too.

<span>
  {{ user?.name }}
</span>

throws an error.

but this works perfectly fine

<span>
  {{ user && user.name }}
</span>

or even with lodash

<span>
  {{ _.get(user, 'name') }}
</span>

I think the argument that everything inside {{ }} must be valid HTML is not acceptable at this point, because whatever inside is not HTML.

@hoomersinpsom
Copy link

@mysticatea can you reopen this issue? It's affecting other cases which will bring back the same discussion again

@mysticatea
Copy link
Member

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.

@stevebaros
Copy link

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

@tmcdos
Copy link

tmcdos commented Jul 9, 2020

Go in packages.json and add this code

It is not packages.json but .eslintrc.js

@bumprat
Copy link

bumprat commented Sep 12, 2020

use v-text ( or v-html )

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

@tmcdos
Copy link

tmcdos commented Sep 13, 2020

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

@bumprat
Copy link

bumprat commented Sep 13, 2020

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

@eithed
Copy link

eithed commented Oct 27, 2020

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.

@mysticatea Just run into the issue with Vue.js 3. Is it possible to reopen the issue?

@ronny1020
Copy link

same problem here.

It can be fixed by add "vue/no-parsing-error": [2, { "invalid-first-character-of-tag-name": false }]" in .eslintrc.js .
However, I hope a better way to fix it.

@trev-dev
Copy link

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.

@ysya
Copy link

ysya commented May 1, 2021

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.

@ota-meshi
Copy link
Member

You need to set parserOptions.vueFeatures.interpolationAsNonHTML to true.

https://github.com/vuejs/vue-eslint-parser#parseroptionsvuefeaturesinterpolationasnonhtml
vuejs/vue-eslint-parser#88

@manhhailua

This comment has been minimized.

@vuejs vuejs locked as resolved and limited conversation to collaborators Sep 23, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests