Skip to content
Open
85 changes: 40 additions & 45 deletions runtime/syntax/vue.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,60 +5,55 @@ detect:

rules:
- default:
start: "<template.*?>"
end: "</template.*?>"
limit-group: symbol.tag
start: "<script.* lang=(ts|'ts'|\"ts\").*>$"
end: "</script>"
rules:
- error: "<[^!].*?>"
- symbol.tag: "(?i)<[/]?(a|a(bbr|ddress|rea|rticle|side|udio)|b|b(ase|d(i|o)|lockquote|r|utton)|ca(nvas|ption)|center|cite|co(de|l|lgroup)|d(ata|atalist|d|el|etails|fn|ialog|l|t)|em|embed|fieldset|fig(caption|ure)|form|iframe|h[1-6]|hr|i|img|in(put|s)|kbd|keygen|label|legend|li|link|ma(in|p|rk)|menu|menuitem|met(a|er)|nav|noscript|o(bject|l|pt(group|ion)|utput)|p|param|picture|pre|progress|q|r(p|t|uby)|s|samp|se(ction|lect)|svg|small|source|span|strong|su(b|p|mmary)|textarea|time|track|u|ul|var|video|wbr)( .*)*?>"
- symbol.tag.extended: "(?i)<[/]?(body|div|html|head(er)?|footer|title|table|t(body|d|h(ead)?|r|foot))( .*)*?>"
- preproc: "(?i)<[/]?(script|style)( .*)*?>"
- special: "&[^;[[:space:]]]*;"

- identifier: "(alt|bgcolor|class|height|href|id|label|longdesc|name|on(click|focus|load|mouseover)|placeholder|size|span|src|style|target|type|value|width)="
- symbol: "[:=]"
- constant.string: "\"[^\"]*\""
- constant.number: "(?i)#[0-9a-fA-F]{6,6}"

- symbol.tag: "<|>"
- constant.string.url: "(ftp(s)?|http(s)?|git|chrome)://[^ ]+"
- comment: "<!--.+?-->"
#- preproc: "<!DOCTYPE.+?>"
- comment.block:
start: "<!\\-\\-"
end: "\\-\\->"
rules: []

# Bootstrap
- symbol.tag.extended: "(?i)<[/]?(b-alert|b-aspect|b-avatar|b-badge|b-icon|b-breadcrumb|b-button-group|b-button-toolbar|b-button|b-calendar|b-card-text|b-card-input|b-card|b-carousel-slide|b-carousel|b-collapse|b-dropdown|b-dropdown-item|b-dropdown-divider|b-embed|b-form-checkbox-group|b-form-checkbox|b-form-datepicker|b-form-file|b-form-group|b-form-input|b-form-radio|b-form-rating|b-form-select|b-form-spinbutton|b-form-tags|b-form-textarea|b-form|b-form-timepicker|b-img-lazy|b-img|b-input-group|b-jumbotron|b-input|b-container|b-row|b-col|b-link|b-list-group|b-list-group-item|b-media|b-modal|b-nav|b-nav-item|b-nav-item-dropdown|b-nav-text|b-nav-form|b-navbar|b-navbar-brand|b-navbar-toggle|b-navbar-nav|b-overlay|b-pagination|b-pagination-nav|b-popover|b-progress|b-progress-bar|b-sidebar|b-skeleton-wrapper|b-skeleton|b-spinner|b-table|b-table-lite|b-table-simple|b-tabs|b-tab|b-time|b-toast|b-tooltip)\\b"
- identifier: "(variant|title|show|shadow|icon|align-h|align-v|label-for|@submit|tag|img-alt|img-src|data-toggle|data-target|aria-controls|aria-expanded|aria-label|aria-disabled|tabindex|:interval|background|img-width|img-height|@sliding-start|@sliding-end|cols|header|@reset)="
- symbol: "[:=]"
# Vue
- symbol.tag.extended: "(?i)<[/]?(component|transition|transition-group|keep-alive|slot)\\b"
- identifier: "(v-text|v-html|v-show|v-if|v-else|v-else-if|v-for|v-on|v-bind|v-model|v-slot|v-pre|v-cloak|v-once|key|ref|is|@click)="
- symbol: "[:=]"
# Vue-router
- symbol.tag.extended: "(?i)<[/]?(router-link|router-view)\\b"
- identifier: "(to|v-slot)="
- symbol: "[:=]"

- include: "typescript"

- default:
start: "<script>"
start: "<script.*>$"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.* is greedy so if you have eg. <script>console.log("hello")</script> this will match the whole line instead of just the start tag. I think the $ should also not be there?

[^>]* would work better (it would still fail if you have something like <script src="src/>.js"> but that's hopefully rare!).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The .*>$ is meant to work with anything like this: <script setup generic="T extends Record<string, unknown>">. Note the extra > from the generic. Matching the last > on the line means we don't need to worry about anything that goes inside the script tag, even >.

<script>console.log("hello")</script> should be very uncommon. The script tag in vue is always supposed to be on a single line. It's technically valid VueJS to write <script>console.log("hello")</script> but I have never seen this in the wild. Actually the default vue linter rules gives me A line break is required before '</script>'. eslint[vue/block-tag-newline](https://eslint.vuejs.org/rules/block-tag-newline.html) if I try.

I initially had a much more complex regex that would cover both cases but it seemed like this very simple one would actually cover almost all cases and remain simple.

Here is the original one Astra came up with which I imagine is a lot more robust but completely unreadable: start: "<script\\s+(?:(?:[^>\"']|\"[^\"]*\"|'[^']*')*\\s)?lang\\s*=\\s*(?:\"ts\"|'ts'|ts\\b)(?:[^>\"']|\"[^\"]*\"|'[^']*')*>"

When asked to simplify I got this start: "<script(?:[ \t].*)?[ \t]lang[ \t]*=[ \t]*['\"]ts['\"].*>[ \t]*$".

The one I landed on is my idea to avoid these wild regex and still work in all VueJS cases I've seen.

end: "</script>"
limit-group: symbol.tag

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why you got rid of this? Now the script tag is not highlighted.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With limit-group, the script tag is highlighted but the typescript/javascript rules don't apply anymore.

image

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a bug. I've opened an issue for it: #4252

rules:
- include: "javascript"

- default:
start: "<script[ ]+lang=(\"ts\"|'ts')>"
end: "</script>"
start: "<style[^>]*>"
end: "</style>"
rules:
- include: "typescript"
- include: "css"

- comment.block:
start: "<!--"
end: "-->"
rules:
- todo: "(TODO|FIXME|NOTE):?"

- default:
start: "<style.*?>"
end: "</style.*?>"
limit-group: symbol.tag
start: "{{"
end: "}}"
rules:
- include: "css"
- include: "javascript"

# Match any html or vue component closing tag
- symbol.tag: "</[A-Za-z][A-Za-z0-9_.:-]*[ \\t]*>"

# A region allows attributes on later lines and arbitrary component names.
- symbol.tag:
start: "<[A-Za-z][A-Za-z0-9_.:-]*"
end: "/?>"
# Skip quoted attribute values so `>` inside them does not end the tag region.
skip: >-
"[^"]*"|'[^']*'
Comment on lines +45 to +46

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't say I'm a fan of this yaml trickery, it took me a moment to understand wtf is going on but I guess the >- is used to avoid escaping either " or ' 😅

rules:
# non-vue attributes
- identifier: "[A-Za-z_][A-Za-z0-9_.:@#\\[\\]-]*"
# built-in vue directives, including v-on:click.stop.prevent, v-slot:header, etc
- special: '(^|[ \t])v-(else-if|text|html|show|if|else|for|on|bind|model|slot|pre|once|memo|cloak)(:[A-Za-z_][A-Za-z0-9_-]*)?(\.[A-Za-z_][A-Za-z0-9_-]*)*\b'
# vue directives with shorthand like @click.once, :class, etc
- special: '(^|[ \t])[@:#][A-Za-z_][A-Za-z0-9_-]*(\.[A-Za-z_][A-Za-z0-9_-]*)*\b'
- symbol: "="
# attributes string content
- constant.string: '"([^"\\]|\\.)*"'
- constant.string: "'([^'\\\\]|\\\\.)*'"
Comment on lines +56 to +57

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does Vue actually support using backslashes to escape things inside string attributes? I know html does not so that would be surprising to me.


- special: "&([A-Za-z][A-Za-z0-9]*|#[0-9]+|#[xX][0-9a-fA-F]+);"