From 8a774379b0102579ce8a7de024b6e7e0b33a1408 Mon Sep 17 00:00:00 2001 From: ota Date: Mon, 18 Nov 2019 20:07:23 +0900 Subject: [PATCH] improve vue-scoped-css/no-parsing-error --- docs/.vuepress/components/rules/index.js | 6 +- lib/rules/no-parsing-error.ts | 51 +++++++++++++-- lib/styles/context/style/index.ts | 79 +++++++++++++++++++----- package-lock.json | 6 ++ package.json | 4 +- tests/lib/rules/no-parsing-error.ts | 36 +++++++++++ 6 files changed, 159 insertions(+), 23 deletions(-) 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: ` + +