diff --git a/docs/.vuepress/components/rules/index.js b/docs/.vuepress/components/rules/index.js
index 0ef1b997..2b98c4ca 100644
--- a/docs/.vuepress/components/rules/index.js
+++ b/docs/.vuepress/components/rules/index.js
@@ -104,7 +104,11 @@ categories.sort((a, b) =>
)
export const DEFAULT_RULES_CONFIG = allRules.reduce((c, r) => {
- c[r.ruleId] = r.initChecked ? "error" : "off"
+ if (r.ruleId === "vue/no-parsing-error") {
+ c[r.ruleId] = "error"
+ } else {
+ c[r.ruleId] = r.initChecked ? "error" : "off"
+ }
return c
}, {})
diff --git a/lib/rules/no-parsing-error.ts b/lib/rules/no-parsing-error.ts
index 909541de..d55af1ad 100644
--- a/lib/rules/no-parsing-error.ts
+++ b/lib/rules/no-parsing-error.ts
@@ -1,5 +1,9 @@
-import { getStyleContexts, getCommentDirectivesReporter } from "../styles"
-import { RuleContext } from "../types"
+import {
+ getStyleContexts,
+ getCommentDirectivesReporter,
+ StyleContext,
+} from "../styles"
+import { RuleContext, LineAndColumnData } from "../types"
import { VCSSParsingError } from "../styles/ast"
module.exports = {
@@ -17,7 +21,7 @@ module.exports = {
type: "problem",
},
create(context: RuleContext) {
- const styles = getStyleContexts(context).filter(style => !style.invalid)
+ const styles = getStyleContexts(context)
if (!styles.length) {
return {}
}
@@ -40,11 +44,48 @@ module.exports = {
})
}
+ /**
+ * Reports the given style
+ * @param {ASTNode} node node to report
+ */
+ function reportInvalidStyle(
+ style: StyleContext & {
+ invalid: {
+ message: string
+ needReport: boolean
+ loc: LineAndColumnData
+ }
+ },
+ ) {
+ reporter.report({
+ node: style.styleElement,
+ loc: style.invalid.loc,
+ message: "Parsing error: {{message}}.",
+ data: {
+ message: style.invalid.message,
+ },
+ })
+ }
+
return {
"Program:exit"() {
for (const style of styles) {
- for (const node of style.cssNode?.errors || []) {
- report(node)
+ if (style.invalid != null) {
+ if (style.invalid.needReport) {
+ reportInvalidStyle(
+ style as StyleContext & {
+ invalid: {
+ message: string
+ needReport: boolean
+ loc: LineAndColumnData
+ }
+ },
+ )
+ }
+ } else {
+ for (const node of style.cssNode?.errors || []) {
+ report(node)
+ }
}
}
},
diff --git a/lib/styles/context/style/index.ts b/lib/styles/context/style/index.ts
index c183f7ac..caf31147 100644
--- a/lib/styles/context/style/index.ts
+++ b/lib/styles/context/style/index.ts
@@ -1,22 +1,51 @@
import { parse } from "../../parser"
-import { AST, SourceCode, RuleContext } from "../../../types"
+import { AST, SourceCode, RuleContext, LineAndColumnData } from "../../../types"
import { VCSSStyleSheet, VCSSNode } from "../../ast"
import { isVCSSContainerNode } from "../../utils/css-nodes"
/**
- * Check whether the templateBody of the program has invalid EOF or not.
- * @param {Program} node the program node to check.
- * @returns {boolean} `true` if it has invalid EOF.
+ * Check whether the program has invalid EOF or not.
*/
-function hasInvalidEOF(node: AST.ESLintProgram) {
+function getInvalidEOFError(
+ context: RuleContext,
+ style: AST.VElement,
+): {
+ inDocumentFragment: boolean
+ error: AST.ParseError
+} | null {
+ const node = context.getSourceCode().ast
const body = node.templateBody
- if (body?.errors == null) {
- return false
+ let errors = body?.errors
+ let inDocumentFragment = false
+ if (errors == null) {
+ if (!context.parserServices.getDocumentFragment) {
+ return null
+ }
+ const df = context.parserServices.getDocumentFragment()
+ inDocumentFragment = true
+ errors = df?.errors
+ if (errors == null) {
+ return null
+ }
+ }
+ const error =
+ errors.find(
+ err =>
+ typeof err.code === "string" &&
+ err.code.startsWith("eof-") &&
+ style.range[0] <= err.index &&
+ err.index < style.range[1],
+ ) ||
+ errors.find(
+ err => typeof err.code === "string" && err.code.startsWith("eof-"),
+ )
+ if (!error) {
+ return null
+ }
+ return {
+ error,
+ inDocumentFragment,
}
- return body.errors.some(
- error =>
- typeof error.code === "string" && error.code.startsWith("eof-"),
- )
}
/**
@@ -84,17 +113,36 @@ interface Visitor {
export class StyleContext {
public readonly styleElement: AST.VElement
public readonly sourceCode: SourceCode
- public readonly invalid: boolean
+ public readonly invalid: {
+ message: string
+ needReport: boolean
+ loc: LineAndColumnData
+ } | null
public readonly scoped: boolean
public readonly lang: string
private readonly cssText: string | null
public readonly cssNode: VCSSStyleSheet | null
- public constructor(style: AST.VElement, sourceCode: SourceCode) {
+ public constructor(style: AST.VElement, context: RuleContext) {
+ const sourceCode = context.getSourceCode()
this.styleElement = style
this.sourceCode = sourceCode
const { startTag, endTag } = style
- this.invalid = endTag == null || hasInvalidEOF(sourceCode.ast)
+ this.invalid = null
+ const eof = getInvalidEOFError(context, style)
+ if (eof) {
+ this.invalid = {
+ message: eof.error.message,
+ needReport: eof.inDocumentFragment,
+ loc: { line: eof.error.lineNumber, column: eof.error.column },
+ }
+ } else if (endTag == null) {
+ this.invalid = {
+ message: "Missing end tag",
+ needReport: true,
+ loc: startTag.loc.end,
+ }
+ }
this.scoped = Boolean(style && isScoped(style))
@@ -157,10 +205,9 @@ function traverseNodes(node: VCSSNode, visitor: Visitor): void {
* @returns {StyleContext[]} the style contexts
*/
export function createStyleContexts(context: RuleContext): StyleContext[] {
- const sourceCode = context.getSourceCode()
const styles = getStyleElements(context)
- return styles.map(style => new StyleContext(style, sourceCode))
+ return styles.map(style => new StyleContext(style, context))
}
/**
diff --git a/package-lock.json b/package-lock.json
index b03e2baf..aa1ca524 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -987,6 +987,12 @@
"integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==",
"dev": true
},
+ "@types/semver": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-6.2.0.tgz",
+ "integrity": "sha512-1OzrNb4RuAzIT7wHSsgZRlMBlNsJl+do6UblR7JMW4oB7bbR+uBEYtUh7gEc/jM84GGilh68lSOokyM/zNUlBA==",
+ "dev": true
+ },
"@typescript-eslint/eslint-plugin": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.6.1.tgz",
diff --git a/package.json b/package.json
index 5752e755..1fc35cac 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "eslint-plugin-vue-scoped-css",
- "version": "0.1.0",
+ "version": "0.2.0",
"description": "ESLint plugin for Scoped CSS in Vue.js",
"main": "dist/index.js",
"scripts": {
@@ -53,6 +53,7 @@
"@types/estree": "0.0.39",
"@types/lodash": "^4.14.147",
"@types/mocha": "^5.2.7",
+ "@types/semver": "^6.2.0",
"babel-eslint": "^10.0.3",
"cpx": "^1.5.0",
"cross-env": "^6.0.3",
@@ -65,6 +66,7 @@
"pack": "^2.2.0",
"raw-loader": "^3.1.0",
"rimraf": "^3.0.0",
+ "semver": "^6.3.0",
"ts-node": "^8.5.2",
"typescript": "^3.7.2",
"vue-eslint-editor": "^0.1.4",
diff --git a/tests/lib/rules/no-parsing-error.ts b/tests/lib/rules/no-parsing-error.ts
index adfb2503..85469057 100644
--- a/tests/lib/rules/no-parsing-error.ts
+++ b/tests/lib/rules/no-parsing-error.ts
@@ -1,5 +1,7 @@
import { RuleTester } from "eslint"
+import semver from "semver"
const rule = require("../../../lib/rules/no-parsing-error")
+const parserVersion = require("vue-eslint-parser/package.json").version
const tester = new RuleTester({
parser: require.resolve("vue-eslint-parser"),
@@ -17,6 +19,9 @@ tester.run("no-parsing-error", rule, {
.item {}
`,
+ `
+
+ `,
],
invalid: [
{
@@ -34,5 +39,36 @@ tester.run("no-parsing-error", rule, {
},
],
},
+ ...(semver.satisfies(parserVersion, ">=7.0.0")
+ ? [
+ {
+ code: `
+
+