oxlint-plugin-oxlint-comments 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2016 Toru Nagashima
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # oxlint-plugin-oxlint-comments
2
+
3
+ [![npm version](https://img.shields.io/npm/v/oxlint-plugin-oxlint-comments.svg)](https://npmx.dev/oxlint-plugin-oxlint-comments)
4
+
5
+ Rules for oxlint directive comments (e.g. `//oxlint-disable-line`).
6
+
7
+ ## Usage
8
+
9
+ - [Documentation](https://github.com/christopher-buss/eslint-plugin-oxlint-comments)
10
+
11
+ ## Semantic Versioning Policy
12
+
13
+ `oxlint-plugin-oxlint-comments` follows [semantic versioning](http://semver.org/).
14
+
15
+ ## Changelog
16
+
17
+ - [GitHub Releases](https://github.com/christopher-buss/eslint-plugin-oxlint-comments/releases)
18
+
19
+ ## Contributing
20
+
21
+ Welcome contributing!
22
+
23
+ Please use GitHub's Issues/PRs.
24
+
25
+ ### Development Tools
26
+
27
+ - `npm test` runs tests and measures coverage.
28
+ - `npm run clean` removes the coverage of the last `npm test` command.
29
+ - `npm run coverage` shows the coverage of the last `npm test` command.
30
+ - `npm run lint` runs the linter for this codebase.
31
+ - `npm run watch` runs tests and measures coverage when source code are changed.
package/index.js ADDED
@@ -0,0 +1,12 @@
1
+ import { createRequire } from "node:module"
2
+ import rules from "./lib/rules.js"
3
+
4
+ const require = createRequire(import.meta.url)
5
+ const { name, version } = require("./package.json")
6
+
7
+ const plugin = {
8
+ meta: { name, version },
9
+ rules,
10
+ }
11
+
12
+ export default plugin
package/lib/configs.js ADDED
@@ -0,0 +1,6 @@
1
+ /** DON'T EDIT THIS FILE; was created by scripts. */
2
+ "use strict"
3
+
4
+ module.exports = {
5
+ recommended: require("./configs/recommended"),
6
+ }
@@ -0,0 +1,181 @@
1
+ /**
2
+ * @author Toru Nagashima <https://github.com/mysticatea>
3
+ * See LICENSE file in root directory for full license.
4
+ */
5
+
6
+ import { lte, parseDirectiveComment } from "./utils.js"
7
+
8
+ const DELIMITER = /[\s,]+/gu
9
+ const pool = new WeakMap()
10
+
11
+ class DisabledArea {
12
+ constructor() {
13
+ this.areas = []
14
+ this.duplicateDisableDirectives = []
15
+ this.unusedEnableDirectives = []
16
+ this.numberOfRelatedDisableDirectives = new Map()
17
+ }
18
+
19
+ _disable(comment, location, ruleIds, kind) {
20
+ if (ruleIds) {
21
+ for (const ruleId of ruleIds) {
22
+ if (this._getArea(ruleId, location) != null) {
23
+ this.duplicateDisableDirectives.push({ comment, ruleId })
24
+ }
25
+
26
+ this.areas.push({
27
+ comment,
28
+ ruleId,
29
+ kind,
30
+ start: location,
31
+ end: null,
32
+ })
33
+ }
34
+ } else {
35
+ if (this._getArea(null, location) != null) {
36
+ this.duplicateDisableDirectives.push({ comment, ruleId: null })
37
+ }
38
+
39
+ this.areas.push({
40
+ comment,
41
+ ruleId: null,
42
+ kind,
43
+ start: location,
44
+ end: null,
45
+ })
46
+ }
47
+ }
48
+
49
+ _enable(comment, location, ruleIds, kind) {
50
+ const relatedDisableDirectives = new Set()
51
+
52
+ if (ruleIds) {
53
+ for (const ruleId of ruleIds) {
54
+ let used = false
55
+
56
+ for (let i = this.areas.length - 1; i >= 0; --i) {
57
+ const area = this.areas[i]
58
+
59
+ if (
60
+ area.end === null &&
61
+ area.kind === kind &&
62
+ area.ruleId === ruleId
63
+ ) {
64
+ relatedDisableDirectives.add(area.comment)
65
+ area.end = location
66
+ used = true
67
+ }
68
+ }
69
+
70
+ if (!used) {
71
+ this.unusedEnableDirectives.push({ comment, ruleId })
72
+ }
73
+ }
74
+ } else {
75
+ let used = false
76
+
77
+ for (let i = this.areas.length - 1; i >= 0; --i) {
78
+ const area = this.areas[i]
79
+
80
+ if (area.end === null && area.kind === kind) {
81
+ relatedDisableDirectives.add(area.comment)
82
+ area.end = location
83
+ used = true
84
+ }
85
+ }
86
+
87
+ if (!used) {
88
+ this.unusedEnableDirectives.push({ comment, ruleId: null })
89
+ }
90
+ }
91
+
92
+ this.numberOfRelatedDisableDirectives.set(
93
+ comment,
94
+ relatedDisableDirectives.size
95
+ )
96
+ }
97
+
98
+ _getArea(ruleId, location) {
99
+ for (let i = this.areas.length - 1; i >= 0; --i) {
100
+ const area = this.areas[i]
101
+
102
+ if (
103
+ (area.ruleId === null || area.ruleId === ruleId) &&
104
+ lte(area.start, location) &&
105
+ (area.end === null || lte(location, area.end))
106
+ ) {
107
+ return area
108
+ }
109
+ }
110
+
111
+ return null
112
+ }
113
+
114
+ _scan(sourceCode) {
115
+ const comments =
116
+ typeof sourceCode.getAllComments === "function"
117
+ ? sourceCode.getAllComments()
118
+ : []
119
+
120
+ for (const comment of comments) {
121
+ const directiveComment = parseDirectiveComment(comment)
122
+ if (directiveComment == null) {
123
+ continue
124
+ }
125
+
126
+ const kind = directiveComment.kind
127
+ if (
128
+ ![
129
+ "oxlint-disable",
130
+ "oxlint-enable",
131
+ "oxlint-disable-line",
132
+ "oxlint-disable-next-line",
133
+ ].includes(kind)
134
+ ) {
135
+ continue
136
+ }
137
+ const ruleIds = directiveComment.value
138
+ ? directiveComment.value.split(DELIMITER)
139
+ : null
140
+
141
+ if (kind === "oxlint-disable") {
142
+ this._disable(comment, comment.loc.start, ruleIds, "block")
143
+ } else if (kind === "oxlint-enable") {
144
+ this._enable(comment, comment.loc.start, ruleIds, "block")
145
+ } else if (kind === "oxlint-disable-line") {
146
+ const line = comment.loc.start.line
147
+ const start = { line, column: 0 }
148
+ const end = { line: line + 1, column: -1 }
149
+
150
+ this._disable(comment, start, ruleIds, "line")
151
+ this._enable(comment, end, ruleIds, "line")
152
+ } else if (kind === "oxlint-disable-next-line") {
153
+ const line = comment.loc.start.line
154
+ const start = { line: line + 1, column: 0 }
155
+ const end = { line: line + 2, column: -1 }
156
+
157
+ this._disable(comment, start, ruleIds, "line")
158
+ this._enable(comment, end, ruleIds, "line")
159
+ }
160
+ }
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Get singleton instance for the given rule context.
166
+ *
167
+ * @param {object} context - The rule context code to get.
168
+ * @returns {DisabledArea} The singleton object for the rule context.
169
+ */
170
+ export function getDisabledArea(context) {
171
+ const sourceCode = context.sourceCode || context.getSourceCode()
172
+ let retv = pool.get(sourceCode.ast)
173
+
174
+ if (retv == null) {
175
+ retv = new DisabledArea()
176
+ retv._scan(sourceCode)
177
+ pool.set(sourceCode.ast, retv)
178
+ }
179
+
180
+ return retv
181
+ }
@@ -0,0 +1,59 @@
1
+ import { parseDirectiveComment } from "./utils.js"
2
+
3
+ /**
4
+ * @typedef {object} DirectiveComment
5
+ * @property {string} kind The kind of directive comment.
6
+ * @property {string} [value] The directive value if it is `oxlint-` comment.
7
+ * @property {string} description The description of the directive comment.
8
+ * @property {object} node The node of the directive comment.
9
+ * @property {object} range The range of the directive comment.
10
+ * @property {object} loc The location of the directive comment.
11
+ */
12
+
13
+ const pool = new WeakMap()
14
+
15
+ /**
16
+ * @param {object} sourceCode - The source code to scan.
17
+ * @returns {DirectiveComment[]} The directive comments.
18
+ */
19
+ function getAllDirectiveCommentsFromAllComments(sourceCode) {
20
+ if (typeof sourceCode.getAllComments !== "function") {
21
+ return []
22
+ }
23
+ return sourceCode
24
+ .getAllComments()
25
+ .map((comment) => ({
26
+ comment,
27
+ directiveComment: parseDirectiveComment(comment),
28
+ }))
29
+ .filter(({ directiveComment }) => Boolean(directiveComment))
30
+ .map(
31
+ ({ comment, directiveComment }) =>
32
+ /** @type {DirectiveComment} */ ({
33
+ kind: directiveComment.kind,
34
+ value: directiveComment.value,
35
+ description: directiveComment.description,
36
+ node: comment,
37
+ range: comment.range,
38
+ loc: comment.loc,
39
+ })
40
+ )
41
+ }
42
+
43
+ /**
44
+ * Get all directive comments for the given rule context.
45
+ *
46
+ * @param {object} context - The rule context to get.
47
+ * @returns {DirectiveComment[]} The all directive comments object for the rule context.
48
+ */
49
+ export function getAllDirectiveComments(context) {
50
+ const sourceCode = context.sourceCode || context.getSourceCode()
51
+ let result = pool.get(sourceCode.ast)
52
+
53
+ if (result == null) {
54
+ result = getAllDirectiveCommentsFromAllComments(sourceCode)
55
+ pool.set(sourceCode.ast, result)
56
+ }
57
+
58
+ return result
59
+ }
@@ -0,0 +1,161 @@
1
+ /**
2
+ * @author Toru Nagashima <https://github.com/mysticatea>
3
+ * See LICENSE file in root directory for full license.
4
+ */
5
+
6
+ import escapeStringRegexp from "escape-string-regexp"
7
+
8
+ const LINE_PATTERN = /[^\r\n\u2028\u2029]*(?:\r\n|[\r\n\u2028\u2029]|$)/gu
9
+
10
+ const DIRECTIVE_PATTERN =
11
+ /^(oxlint-(?:enable|disable(?:(?:-next)?-line)?))(?:\s|$)/u
12
+
13
+ /**
14
+ * Make the location ignoring `oxlint-disable` comments.
15
+ *
16
+ * @param {object} location - The location to convert.
17
+ * @returns {object} Converted location.
18
+ */
19
+ export function toForceLocation(location) {
20
+ return {
21
+ start: {
22
+ line: location.start.line,
23
+ column: 0,
24
+ },
25
+ end: location.end,
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Calculate the location of the given rule in the given comment token.
31
+ *
32
+ * @param {Token} comment - The comment token to calculate.
33
+ * @param {string|null} ruleId - The rule name to calculate.
34
+ * @returns {object} The location of the given information.
35
+ */
36
+ export function toRuleIdLocation(comment, ruleId) {
37
+ const commentLoc = comment.loc
38
+ if (ruleId == null) {
39
+ return toForceLocation(commentLoc)
40
+ }
41
+
42
+ const lines = comment.value.match(LINE_PATTERN)
43
+ const ruleIdPattern = new RegExp(
44
+ `([\\s,]|^)${escapeStringRegexp(ruleId)}(?:[\\s,]|$)`,
45
+ "u"
46
+ )
47
+
48
+ {
49
+ const m = ruleIdPattern.exec(lines[0])
50
+ if (m != null) {
51
+ const start = commentLoc.start
52
+ return {
53
+ start: {
54
+ line: start.line,
55
+ column: 2 + start.column + m.index + m[1].length,
56
+ },
57
+ end: {
58
+ line: start.line,
59
+ column:
60
+ 2 +
61
+ start.column +
62
+ m.index +
63
+ m[1].length +
64
+ ruleId.length,
65
+ },
66
+ }
67
+ }
68
+ }
69
+
70
+ for (let i = 1; i < lines.length; ++i) {
71
+ const m = ruleIdPattern.exec(lines[i])
72
+ if (m != null) {
73
+ const start = commentLoc.start
74
+ return {
75
+ start: {
76
+ line: start.line + i,
77
+ column: m.index + m[1].length,
78
+ },
79
+ end: {
80
+ line: start.line + i,
81
+ column: m.index + m[1].length + ruleId.length,
82
+ },
83
+ }
84
+ }
85
+ }
86
+
87
+ /*istanbul ignore next : foolproof */
88
+ return commentLoc
89
+ }
90
+
91
+ /**
92
+ * Checks `a` is less than `b` or `a` equals `b`.
93
+ *
94
+ * @param {{line: number, column: number}} a - A location to compare.
95
+ * @param {{line: number, column: number}} b - Another location to compare.
96
+ * @returns {boolean} `true` if `a` is less than `b` or `a` equals `b`.
97
+ */
98
+ export function lte(a, b) {
99
+ return a.line < b.line || (a.line === b.line && a.column <= b.column)
100
+ }
101
+
102
+ /**
103
+ * Parse the given comment token as a directive comment.
104
+ *
105
+ * @param {Token} comment - The comment token to parse.
106
+ * @returns {{kind: string, value: string, description: string | null}|null} The parsed data of the given comment. If `null`, it is not a directive comment.
107
+ */
108
+ export function parseDirectiveComment(comment) {
109
+ const parsed = parseDirectiveText(comment.value)
110
+ if (!parsed) {
111
+ return null
112
+ }
113
+
114
+ if (
115
+ parsed.kind === "oxlint-disable-line" &&
116
+ comment.loc.start.line !== comment.loc.end.line
117
+ ) {
118
+ // disable-line comment should not span multiple lines.
119
+ return null
120
+ }
121
+
122
+ return parsed
123
+ }
124
+
125
+ /**
126
+ * Parse the given text as a directive comment.
127
+ *
128
+ * @param {string} textToParse - The text to parse.
129
+ * @returns {{kind: string, value: string, description: string | null}|null} The parsed data of the given comment. If `null`, it is not a directive comment.
130
+ */
131
+ export function parseDirectiveText(textToParse) {
132
+ const { text, description } = divideDirectiveComment(textToParse)
133
+ const match = DIRECTIVE_PATTERN.exec(text)
134
+
135
+ if (!match) {
136
+ return null
137
+ }
138
+ const directiveText = match[1]
139
+
140
+ const directiveValue = text.slice(match.index + directiveText.length)
141
+
142
+ return {
143
+ kind: directiveText,
144
+ value: directiveValue.trim(),
145
+ description,
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Divides and trims description text and directive comments.
151
+ * @param {string} value The comment text to strip.
152
+ * @returns {{text: string, description: string | null}} The stripped text.
153
+ */
154
+ function divideDirectiveComment(value) {
155
+ const divided = value.split(/\s-{2,}\s/u)
156
+ const text = divided[0].trim()
157
+ return {
158
+ text,
159
+ description: divided.length > 1 ? divided[1].trim() : null,
160
+ }
161
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * @author Toru Nagashima <https://github.com/mysticatea>
3
+ * See LICENSE file in root directory for full license.
4
+ */
5
+
6
+ import { getDisabledArea } from "../internal/disabled-area.js"
7
+ import { lte, toRuleIdLocation } from "../internal/utils.js"
8
+
9
+ export default {
10
+ meta: {
11
+ docs: {
12
+ description:
13
+ "require a `oxlint-enable` comment for every `oxlint-disable` comment",
14
+ category: "Best Practices",
15
+ recommended: true,
16
+ url: "https://github.com/christopher-buss/eslint-plugin-oxlint-comments/blob/main/docs/rules/disable-enable-pair.md",
17
+ },
18
+ fixable: null,
19
+ messages: {
20
+ missingPair: "Requires 'oxlint-enable' directive.",
21
+ missingRulePair:
22
+ "Requires 'oxlint-enable' directive for '{{ruleId}}'.",
23
+ },
24
+ schema: [
25
+ {
26
+ type: "object",
27
+ properties: {
28
+ allowWholeFile: {
29
+ type: "boolean",
30
+ },
31
+ },
32
+ additionalProperties: false,
33
+ },
34
+ ],
35
+ type: "suggestion",
36
+ },
37
+
38
+ createOnce(context) {
39
+ return {
40
+ Program() {
41
+ const allowWholeFile =
42
+ context.options &&
43
+ context.options[0] &&
44
+ context.options[0].allowWholeFile
45
+ const disabledArea = getDisabledArea(context)
46
+ const sourceCode =
47
+ context.sourceCode || context.getSourceCode()
48
+
49
+ const firstToken =
50
+ sourceCode.ast &&
51
+ sourceCode.ast.tokens &&
52
+ sourceCode.ast.tokens[0]
53
+
54
+ if (allowWholeFile && !firstToken) {
55
+ return
56
+ }
57
+
58
+ for (const area of disabledArea.areas) {
59
+ if (area.end != null) {
60
+ continue
61
+ }
62
+ if (
63
+ allowWholeFile &&
64
+ firstToken &&
65
+ lte(area.start, firstToken.loc.start)
66
+ ) {
67
+ continue
68
+ }
69
+
70
+ context.report({
71
+ loc: toRuleIdLocation(area.comment, area.ruleId),
72
+ messageId: area.ruleId
73
+ ? "missingRulePair"
74
+ : "missingPair",
75
+ data: area,
76
+ })
77
+ }
78
+ },
79
+ }
80
+ },
81
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * @author Toru Nagashima <https://github.com/mysticatea>
3
+ * See LICENSE file in root directory for full license.
4
+ */
5
+
6
+ import { getDisabledArea } from "../internal/disabled-area.js"
7
+ import { toForceLocation } from "../internal/utils.js"
8
+
9
+ export default {
10
+ meta: {
11
+ docs: {
12
+ description:
13
+ "disallow a `oxlint-enable` comment for multiple `oxlint-disable` comments",
14
+ category: "Best Practices",
15
+ recommended: true,
16
+ url: "https://github.com/christopher-buss/eslint-plugin-oxlint-comments/blob/main/docs/rules/no-aggregating-enable.md",
17
+ },
18
+ fixable: null,
19
+ messages: {
20
+ aggregatingEnable:
21
+ "This `oxlint-enable` comment affects {{count}} `oxlint-disable` comments. An `oxlint-enable` comment should be for an `oxlint-disable` comment.",
22
+ },
23
+ schema: [],
24
+ type: "suggestion",
25
+ },
26
+
27
+ createOnce(context) {
28
+ return {
29
+ Program() {
30
+ const disabledArea = getDisabledArea(context)
31
+
32
+ for (const entry of disabledArea.numberOfRelatedDisableDirectives) {
33
+ const comment = entry[0]
34
+ const count = entry[1]
35
+
36
+ if (count >= 2) {
37
+ context.report({
38
+ loc: toForceLocation(comment.loc),
39
+ messageId: "aggregatingEnable",
40
+ data: { count },
41
+ })
42
+ }
43
+ }
44
+ },
45
+ }
46
+ },
47
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @author Toru Nagashima <https://github.com/mysticatea>
3
+ * See LICENSE file in root directory for full license.
4
+ */
5
+
6
+ import { getDisabledArea } from "../internal/disabled-area.js"
7
+ import { toRuleIdLocation } from "../internal/utils.js"
8
+
9
+ export default {
10
+ meta: {
11
+ docs: {
12
+ description: "disallow duplicate `oxlint-disable` comments",
13
+ category: "Best Practices",
14
+ recommended: true,
15
+ url: "https://github.com/christopher-buss/eslint-plugin-oxlint-comments/blob/main/docs/rules/no-duplicate-disable.md",
16
+ },
17
+ fixable: null,
18
+ messages: {
19
+ duplicate: "Oxlint rules have been disabled already.",
20
+ duplicateRule: "'{{ruleId}}' rule has been disabled already.",
21
+ },
22
+ schema: [],
23
+ type: "problem",
24
+ },
25
+
26
+ createOnce(context) {
27
+ return {
28
+ Program() {
29
+ const disabledArea = getDisabledArea(context)
30
+
31
+ for (const item of disabledArea.duplicateDisableDirectives) {
32
+ context.report({
33
+ loc: toRuleIdLocation(item.comment, item.ruleId),
34
+ messageId: item.ruleId
35
+ ? "duplicateRule"
36
+ : "duplicate",
37
+ data: item,
38
+ })
39
+ }
40
+ },
41
+ }
42
+ },
43
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @author Toru Nagashima <https://github.com/mysticatea>
3
+ * See LICENSE file in root directory for full license.
4
+ */
5
+
6
+ import ignore from "ignore"
7
+ import { getDisabledArea } from "../internal/disabled-area.js"
8
+ import { toRuleIdLocation } from "../internal/utils.js"
9
+
10
+ export default {
11
+ meta: {
12
+ docs: {
13
+ description:
14
+ "disallow `oxlint-disable` comments about specific rules",
15
+ category: "Stylistic Issues",
16
+ recommended: false,
17
+ url: "https://github.com/christopher-buss/eslint-plugin-oxlint-comments/blob/main/docs/rules/no-restricted-disable.md",
18
+ },
19
+ fixable: null,
20
+ messages: {
21
+ disallow: "Disabling '{{ruleId}}' is not allowed.",
22
+ },
23
+ schema: {
24
+ type: "array",
25
+ items: { type: "string" },
26
+ uniqueItems: true,
27
+ },
28
+ type: "suggestion",
29
+ },
30
+
31
+ createOnce(context) {
32
+ return {
33
+ Program() {
34
+ if (!context.options || context.options.length === 0) {
35
+ return
36
+ }
37
+
38
+ const ig = ignore()
39
+ for (const pattern of context.options) {
40
+ ig.add(pattern)
41
+ }
42
+ const disabledArea = getDisabledArea(context)
43
+
44
+ for (const area of disabledArea.areas) {
45
+ if (area.ruleId == null || ig.ignores(area.ruleId)) {
46
+ context.report({
47
+ loc: toRuleIdLocation(
48
+ area.comment,
49
+ area.ruleId
50
+ ),
51
+ messageId: "disallow",
52
+ data: {
53
+ ruleId:
54
+ area.ruleId || String(context.options),
55
+ },
56
+ })
57
+ }
58
+ }
59
+ },
60
+ }
61
+ },
62
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @author Toru Nagashima <https://github.com/mysticatea>
3
+ * See LICENSE file in root directory for full license.
4
+ */
5
+
6
+ import { getAllDirectiveComments } from "../internal/get-all-directive-comments.js"
7
+ import { toForceLocation } from "../internal/utils.js"
8
+
9
+ export default {
10
+ meta: {
11
+ docs: {
12
+ description:
13
+ "disallow `oxlint-disable` comments without rule names",
14
+ category: "Best Practices",
15
+ recommended: true,
16
+ url: "https://github.com/christopher-buss/eslint-plugin-oxlint-comments/blob/main/docs/rules/no-unlimited-disable.md",
17
+ },
18
+ fixable: null,
19
+ messages: {
20
+ unexpected:
21
+ "Unexpected unlimited '{{kind}}' comment. Specify some rule names to disable.",
22
+ },
23
+ schema: [],
24
+ type: "suggestion",
25
+ },
26
+
27
+ createOnce(context) {
28
+ return {
29
+ Program() {
30
+ for (const directiveComment of getAllDirectiveComments(
31
+ context
32
+ )) {
33
+ const kind = directiveComment.kind
34
+ if (
35
+ kind !== "oxlint-disable" &&
36
+ kind !== "oxlint-disable-line" &&
37
+ kind !== "oxlint-disable-next-line"
38
+ ) {
39
+ continue
40
+ }
41
+ if (!directiveComment.value) {
42
+ context.report({
43
+ loc: toForceLocation(directiveComment.loc),
44
+ messageId: "unexpected",
45
+ data: { kind: directiveComment.kind },
46
+ })
47
+ }
48
+ }
49
+ },
50
+ }
51
+ },
52
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @author Toru Nagashima <https://github.com/mysticatea>
3
+ * See LICENSE file in root directory for full license.
4
+ */
5
+
6
+ import { getDisabledArea } from "../internal/disabled-area.js"
7
+ import { toRuleIdLocation } from "../internal/utils.js"
8
+
9
+ export default {
10
+ meta: {
11
+ docs: {
12
+ description: "disallow unused `oxlint-enable` comments",
13
+ category: "Best Practices",
14
+ recommended: true,
15
+ url: "https://github.com/christopher-buss/eslint-plugin-oxlint-comments/blob/main/docs/rules/no-unused-enable.md",
16
+ },
17
+ fixable: null,
18
+ messages: {
19
+ unused: "Oxlint rules are re-enabled but those have not been disabled.",
20
+ unusedRule:
21
+ "'{{ruleId}}' rule is re-enabled but it has not been disabled.",
22
+ },
23
+ schema: [],
24
+ type: "problem",
25
+ },
26
+
27
+ createOnce(context) {
28
+ return {
29
+ Program() {
30
+ const disabledArea = getDisabledArea(context)
31
+
32
+ for (const item of disabledArea.unusedEnableDirectives) {
33
+ context.report({
34
+ loc: toRuleIdLocation(item.comment, item.ruleId),
35
+ messageId: item.ruleId ? "unusedRule" : "unused",
36
+ data: item,
37
+ })
38
+ }
39
+ },
40
+ }
41
+ },
42
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @author Toru Nagashima <https://github.com/mysticatea>
3
+ * See LICENSE file in root directory for full license.
4
+ */
5
+
6
+ import { getAllDirectiveComments } from "../internal/get-all-directive-comments.js"
7
+ import { toForceLocation } from "../internal/utils.js"
8
+
9
+ export default {
10
+ meta: {
11
+ docs: {
12
+ description: "disallow oxlint directive-comments",
13
+ category: "Stylistic Issues",
14
+ recommended: false,
15
+ url: "https://github.com/christopher-buss/eslint-plugin-oxlint-comments/blob/main/docs/rules/no-use.md",
16
+ },
17
+ fixable: null,
18
+ messages: {
19
+ disallow: "Unexpected oxlint directive comment.",
20
+ },
21
+ schema: [
22
+ {
23
+ type: "object",
24
+ properties: {
25
+ allow: {
26
+ type: "array",
27
+ items: {
28
+ enum: [
29
+ "oxlint-disable",
30
+ "oxlint-disable-line",
31
+ "oxlint-disable-next-line",
32
+ "oxlint-enable",
33
+ ],
34
+ },
35
+ additionalItems: false,
36
+ uniqueItems: true,
37
+ },
38
+ },
39
+ additionalProperties: false,
40
+ },
41
+ ],
42
+ type: "suggestion",
43
+ },
44
+
45
+ createOnce(context) {
46
+ return {
47
+ Program() {
48
+ const allowed = new Set(
49
+ (context.options && context.options[0] && context.options[0].allow) || []
50
+ )
51
+ for (const directiveComment of getAllDirectiveComments(
52
+ context
53
+ )) {
54
+ if (!allowed.has(directiveComment.kind)) {
55
+ context.report({
56
+ loc: toForceLocation(directiveComment.loc),
57
+ messageId: "disallow",
58
+ })
59
+ }
60
+ }
61
+ },
62
+ }
63
+ },
64
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * @author Yosuke Ota <https://github.com/ota-meshi>
3
+ * See LICENSE file in root directory for full license.
4
+ */
5
+
6
+ import { getAllDirectiveComments } from "../internal/get-all-directive-comments.js"
7
+ import { toForceLocation } from "../internal/utils.js"
8
+
9
+ export default {
10
+ meta: {
11
+ docs: {
12
+ description:
13
+ "require include descriptions in oxlint directive-comments",
14
+ category: "Stylistic Issues",
15
+ recommended: false,
16
+ url: "https://github.com/christopher-buss/eslint-plugin-oxlint-comments/blob/main/docs/rules/require-description.md",
17
+ },
18
+ fixable: null,
19
+ messages: {
20
+ missingDescription:
21
+ "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.",
22
+ },
23
+ schema: [
24
+ {
25
+ type: "object",
26
+ properties: {
27
+ ignore: {
28
+ type: "array",
29
+ items: {
30
+ enum: [
31
+ "oxlint-disable",
32
+ "oxlint-disable-line",
33
+ "oxlint-disable-next-line",
34
+ "oxlint-enable",
35
+ ],
36
+ },
37
+ additionalItems: false,
38
+ uniqueItems: true,
39
+ },
40
+ },
41
+ additionalProperties: false,
42
+ },
43
+ ],
44
+ type: "suggestion",
45
+ },
46
+
47
+ createOnce(context) {
48
+ return {
49
+ Program() {
50
+ const ignores = new Set(
51
+ (context.options && context.options[0] && context.options[0].ignore) || []
52
+ )
53
+ for (const directiveComment of getAllDirectiveComments(
54
+ context
55
+ )) {
56
+ if (ignores.has(directiveComment.kind)) {
57
+ continue
58
+ }
59
+ if (!directiveComment.description) {
60
+ context.report({
61
+ loc: toForceLocation(directiveComment.loc),
62
+ messageId: "missingDescription",
63
+ })
64
+ }
65
+ }
66
+ },
67
+ }
68
+ },
69
+ }
package/lib/rules.js ADDED
@@ -0,0 +1,21 @@
1
+ /** DON'T EDIT THIS FILE; was created by scripts. */
2
+
3
+ import disableEnablePair from "./rules/disable-enable-pair.js"
4
+ import noAggregatingEnable from "./rules/no-aggregating-enable.js"
5
+ import noDuplicateDisable from "./rules/no-duplicate-disable.js"
6
+ import noRestrictedDisable from "./rules/no-restricted-disable.js"
7
+ import noUnlimitedDisable from "./rules/no-unlimited-disable.js"
8
+ import noUnusedEnable from "./rules/no-unused-enable.js"
9
+ import noUse from "./rules/no-use.js"
10
+ import requireDescription from "./rules/require-description.js"
11
+
12
+ export default {
13
+ "disable-enable-pair": disableEnablePair,
14
+ "no-aggregating-enable": noAggregatingEnable,
15
+ "no-duplicate-disable": noDuplicateDisable,
16
+ "no-restricted-disable": noRestrictedDisable,
17
+ "no-unlimited-disable": noUnlimitedDisable,
18
+ "no-unused-enable": noUnusedEnable,
19
+ "no-use": noUse,
20
+ "require-description": requireDescription,
21
+ }
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "oxlint-plugin-oxlint-comments",
3
+ "version": "0.1.0",
4
+ "description": "Rules for oxlint directive comments.",
5
+ "keywords": [
6
+ "oxlint",
7
+ "plugin",
8
+ "comment",
9
+ "comments",
10
+ "directive",
11
+ "oxlint-enable",
12
+ "oxlint-disable",
13
+ "oxlint-disable-line",
14
+ "oxlint-disable-next-line"
15
+ ],
16
+ "homepage": "https://github.com/christopher-buss/eslint-plugin-oxlint-comments#readme",
17
+ "bugs": {
18
+ "url": "https://github.com/christopher-buss/eslint-plugin-oxlint-comments/issues"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/christopher-buss/eslint-plugin-oxlint-comments"
23
+ },
24
+ "license": "MIT",
25
+ "author": "Toru Nagashima",
26
+ "type": "module",
27
+ "exports": {
28
+ ".": "./index.js",
29
+ "./package.json": "./package.json"
30
+ },
31
+ "main": "index.js",
32
+ "files": [
33
+ "index.js",
34
+ "lib"
35
+ ],
36
+ "dependencies": {
37
+ "escape-string-regexp": "^4.0.0",
38
+ "ignore": "^7.0.5",
39
+ "oxlint": ">=0.16.7"
40
+ },
41
+ "devDependencies": {
42
+ "bumpp": "^11.0.0",
43
+ "prettier": "2.8.8",
44
+ "rimraf": "^3.0.2",
45
+ "vitepress": "^1.6.4",
46
+ "vitest": "^3.2.1"
47
+ },
48
+ "engines": {
49
+ "node": ">=22.0.0"
50
+ },
51
+ "scripts": {
52
+ "clean": "rimraf docs/.vitepress/cache",
53
+ "debug": "vitest",
54
+ "docs:build": "vitepress build docs",
55
+ "docs:watch": "vitepress dev docs",
56
+ "format": "npm run -s format:prettier -- --write",
57
+ "format:check": "npm run -s format:prettier -- --check",
58
+ "format:prettier": "prettier .",
59
+ "postversion": "git push && git push --tags",
60
+ "preversion": "npm test",
61
+ "release": "bumpp",
62
+ "test": "vitest run",
63
+ "version": "node scripts/update && git add .",
64
+ "watch": "vitest --watch"
65
+ }
66
+ }