Skip to content

Commit 7112415

Browse files
authored
Add no-parsing-error rule (#3)
1 parent 340b35b commit 7112415

File tree

11 files changed

+141
-9
lines changed

11 files changed

+141
-9
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ Enforce all the rules in this category with:
9393

9494
| | Rule ID | Description |
9595
|:---|:--------|:------------|
96+
| | [vue-scoped-css/no-parsing-error](./docs/rules/no-parsing-error.md) | Disallow parsing errors in `<style>` |
9697
| | [vue-scoped-css/no-unused-selector](./docs/rules/no-unused-selector.md) | Reports selectors defined in Scoped CSS not used in `<template>`. |
9798
| | [vue-scoped-css/require-scoped](./docs/rules/require-scoped.md) | Enforce the `<style>` tags to has the `scoped` attribute. |
9899

docs/rules/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Enforce all the rules in this category with:
2828

2929
| Rule ID | Description | |
3030
|:--------|:------------|:---|
31+
| [vue-scoped-css/no-parsing-error](./no-parsing-error.md) | Disallow parsing errors in `<style>` | |
3132
| [vue-scoped-css/no-unused-selector](./no-unused-selector.md) | Reports selectors defined in Scoped CSS not used in `<template>`. | |
3233
| [vue-scoped-css/require-scoped](./require-scoped.md) | Enforce the `<style>` tags to has the `scoped` attribute. | |
3334

docs/rules/no-parsing-error.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
pageClass: "rule-details"
3+
sidebarDepth: 0
4+
title: "vue-scoped-css/no-parsing-error"
5+
description: "Disallow parsing errors in `<style>`"
6+
---
7+
# vue-scoped-css/no-parsing-error
8+
9+
> Disallow parsing errors in `<style>`
10+
11+
- :gear: This rule is included in `"plugin:vue-scoped-css/recommended"` and `"plugin:vue-scoped-css/all"`.
12+
13+
This rule reports syntax errors in `<style>`.
14+
15+
<eslint-code-block :rules="{'vue-scoped-css/no-parsing-error': ['error']}">
16+
17+
```vue
18+
<style scoped>
19+
/* ✗ BAD */
20+
.item {
21+
</style>
22+
```
23+
24+
</eslint-code-block>
25+
26+
## :books: Further reading
27+
28+
- None
29+
30+
## Implementation
31+
32+
- [Rule source](https://github.com/ota-meshi/eslint-plugin-vue-scoped-css/blob/master/lib/rules/no-parsing-error.ts)
33+
- [Test source](https://github.com/ota-meshi/eslint-plugin-vue-scoped-css/blob/master/tests/lib/rules/no-parsing-error.js)

lib/rules/no-parsing-error.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { getStyleContexts, getCommentDirectivesReporter } from "../styles"
2+
import { RuleContext } from "../types"
3+
import { VCSSParsingError } from "../styles/ast"
4+
5+
module.exports = {
6+
meta: {
7+
docs: {
8+
description: "Disallow parsing errors in `<style>`",
9+
category: "recommended",
10+
default: "warn",
11+
url:
12+
"https://github.com/ota-meshi/eslint-plugin-vue-scoped-css/blob/v0.0.0/docs/rules/no-parsing-error.ts.md",
13+
},
14+
fixable: null,
15+
messages: {},
16+
schema: [],
17+
type: "problem",
18+
},
19+
create(context: RuleContext) {
20+
const styles = getStyleContexts(context).filter(style => !style.invalid)
21+
if (!styles.length) {
22+
return {}
23+
}
24+
const reporter = getCommentDirectivesReporter(context)
25+
26+
/**
27+
* Reports the given node
28+
* @param {ASTNode} node node to report
29+
*/
30+
function report(node: VCSSParsingError) {
31+
reporter.report({
32+
node,
33+
loc: node.loc.start,
34+
message: "Parsing error: {{message}}.",
35+
data: {
36+
message: node.message.endsWith(".")
37+
? node.message.slice(0, -1)
38+
: node.message,
39+
},
40+
})
41+
}
42+
43+
return {
44+
"Program:exit"() {
45+
for (const style of styles) {
46+
for (const node of style.cssNode?.errors || []) {
47+
report(node)
48+
}
49+
}
50+
},
51+
}
52+
},
53+
}

lib/styles/ast.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ class HasParentNode<
9090
* The CSS Parsing Error.
9191
*/
9292
export class VCSSParsingError extends Node<"VCSSParsingError"> {
93+
public readonly message: string
9394
/**
9495
* constructor.
9596
* @param {PostCSSDeclaration} node The node.
@@ -106,9 +107,11 @@ export class VCSSParsingError extends Node<"VCSSParsingError"> {
106107
end: number,
107108
props: {
108109
lang: string
110+
message: string
109111
},
110112
) {
111113
super(node, "VCSSParsingError", loc, start, end, props.lang)
114+
this.message = props.message
112115
}
113116
}
114117

@@ -118,7 +121,7 @@ export class VCSSParsingError extends Node<"VCSSParsingError"> {
118121
export class VCSSStyleSheet extends Node<"VCSSStyleSheet"> {
119122
public nodes: VCSSNode[]
120123
public comments: VCSSCommentNode[]
121-
public readonly errors: Node<string>[]
124+
public readonly errors: VCSSParsingError[]
122125
/**
123126
* constructor.
124127
* @param {PostCSSRoot} node The node.
@@ -136,7 +139,7 @@ export class VCSSStyleSheet extends Node<"VCSSStyleSheet"> {
136139
props: {
137140
nodes?: VCSSNode[]
138141
comments?: VCSSCommentNode[]
139-
errors?: Node<string>[]
142+
errors?: VCSSParsingError[]
140143
lang: string
141144
},
142145
) {

lib/styles/context/comment-directive/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
LineAndColumnData,
33
ReportDescriptor,
44
RuleContext,
5+
SourceLocation,
56
} from "../../../types"
67
import { StyleContext } from "../style"
78
import { VCSSCommentNode } from "../../ast"
@@ -211,7 +212,7 @@ export class CommentDirectives {
211212
if (!loc) {
212213
return false
213214
}
214-
const locStart = loc.start
215+
const locStart = (loc as SourceLocation).start || loc
215216

216217
const disableLine = this._disableLines[locStart.line]
217218
if (disableLine) {

lib/styles/parser/css-parser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,9 @@ export class CSSParser {
8484
errorIndex,
8585
{
8686
lang: this.lang,
87+
message,
8788
},
8889
)
89-
;(errorNode as any).message = message
9090

9191
const startIndex = sourceCode.getIndexFromLoc(offsetLocation)
9292
const endIndex = startIndex + css.length

lib/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,10 @@ export interface RuleContext {
7474
parserServices: ParserServices
7575
}
7676
export type ReportDescriptor = {
77-
loc?: SourceLocation
77+
loc?: SourceLocation | { line: number; column: number }
7878
node?: AST.HasLocation
7979
messageId?: string
80+
message?: string
8081
data?: { [key: string]: any }
8182
}
8283
export interface SourceCode {

lib/utils/rules.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { Rule } from "../types"
22

33
const baseRules = [
4+
{
5+
rule: require("../rules/no-parsing-error"),
6+
ruleName: "no-parsing-error",
7+
ruleId: "vue-scoped-css/no-parsing-error",
8+
},
49
{
510
rule: require("../rules/no-unused-selector"),
611
ruleName: "no-unused-selector",
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { RuleTester } from "eslint"
2+
const rule = require("../../../lib/rules/no-parsing-error")
3+
4+
const tester = new RuleTester({
5+
parser: require.resolve("vue-eslint-parser"),
6+
parserOptions: {
7+
ecmaVersion: 2019,
8+
sourceType: "module",
9+
},
10+
})
11+
12+
tester.run("no-parsing-error", rule, {
13+
valid: [
14+
`
15+
<style scoped>
16+
.item {}
17+
</style>
18+
`,
19+
],
20+
invalid: [
21+
{
22+
code: `
23+
<style scoped>
24+
.item {
25+
</style>
26+
`,
27+
errors: [
28+
{
29+
message: "Parsing error: Unclosed block.",
30+
line: 3,
31+
column: 13,
32+
},
33+
],
34+
},
35+
],
36+
})

0 commit comments

Comments
 (0)