@kws3/eslint-plugin-svelte4 1.0.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 +21 -0
- package/package.json +26 -0
- package/src/index.js +28 -0
- package/src/rules/no-datalist-scaffold.js +33 -0
- package/src/rules/no-date-string-arg.js +23 -0
- package/src/rules/no-raw-text.js +235 -0
- package/src/utils/index.js +13 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 KWS3 Media Ltd
|
|
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/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kws3/eslint-plugin-svelte4",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Kws3 specific svelte4 eslint rules",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"author": "Khaled Ahmed <me.khaled@gmail.com>",
|
|
7
|
+
"contributors": [
|
|
8
|
+
"Sabir Mohammed <sabirveli@gmail.com>",
|
|
9
|
+
"Suneesh S K <suneeshsk3@gmail.com>"
|
|
10
|
+
],
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"eslint",
|
|
16
|
+
"rules",
|
|
17
|
+
"svelte",
|
|
18
|
+
"svelte4"
|
|
19
|
+
],
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"eslint": "^7.0.0 || ^8.0.0-0",
|
|
22
|
+
"svelte": "^4.0.0",
|
|
23
|
+
"svelte-eslint-parser": "0.33.1"
|
|
24
|
+
},
|
|
25
|
+
"gitHead": "1f27bb086a10679e03f1657dd1f86800fffe1ab5"
|
|
26
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const noDateStringArg = require("./rules/no-date-string-arg");
|
|
2
|
+
const noDatalistScaffold = require("./rules/no-datalist-scaffold");
|
|
3
|
+
const noRawtext = require("./rules/no-raw-text");
|
|
4
|
+
|
|
5
|
+
const allRules = [noDateStringArg, noDatalistScaffold, noRawtext];
|
|
6
|
+
|
|
7
|
+
const configs = {
|
|
8
|
+
base: {
|
|
9
|
+
plugins: ["@kws3/svelte3"],
|
|
10
|
+
overrides: [
|
|
11
|
+
{
|
|
12
|
+
files: ["*.svelte"],
|
|
13
|
+
parser: require.resolve("svelte-eslint-parser"),
|
|
14
|
+
rules: allRules.reduce((obj, r) => {
|
|
15
|
+
obj[r.meta.docs.ruleId] = r.meta.recommended ? "error" : "off";
|
|
16
|
+
return obj;
|
|
17
|
+
}, {}),
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const rules = allRules.reduce((obj, r) => {
|
|
24
|
+
obj[r.meta.docs.ruleName] = r;
|
|
25
|
+
return obj;
|
|
26
|
+
}, {});
|
|
27
|
+
|
|
28
|
+
module.exports = { configs, rules };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const createRule = require("../utils");
|
|
2
|
+
|
|
3
|
+
module.exports = createRule("no-datalist-scaffold", {
|
|
4
|
+
meta: {
|
|
5
|
+
docs: {
|
|
6
|
+
description: "Disallow wrapping DataList with ViewScaffold",
|
|
7
|
+
},
|
|
8
|
+
recommended: true,
|
|
9
|
+
schema: [],
|
|
10
|
+
},
|
|
11
|
+
create: function (context) {
|
|
12
|
+
return {
|
|
13
|
+
SvelteElement(node) {
|
|
14
|
+
if (node.kind === "component" && node.name.name === "ViewScaffold") {
|
|
15
|
+
if (checkChildComponent(node.children, "DataList")) {
|
|
16
|
+
context.report(node, "Do not use Viewscaffold to wrap DataList");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
function checkChildComponent(node, childComponentName) {
|
|
25
|
+
return node.some(
|
|
26
|
+
(item) =>
|
|
27
|
+
item.type === "SvelteElement" &&
|
|
28
|
+
item.name &&
|
|
29
|
+
item.name.name === childComponentName
|
|
30
|
+
)
|
|
31
|
+
? true
|
|
32
|
+
: false;
|
|
33
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const createRule = require("../utils");
|
|
2
|
+
|
|
3
|
+
module.exports = createRule("no-date-string-arg", {
|
|
4
|
+
meta: {
|
|
5
|
+
docs: {
|
|
6
|
+
description: "Disallow Date constructor with string argument",
|
|
7
|
+
},
|
|
8
|
+
recommended: true,
|
|
9
|
+
schema: [],
|
|
10
|
+
},
|
|
11
|
+
create: function (context) {
|
|
12
|
+
return {
|
|
13
|
+
NewExpression(node) {
|
|
14
|
+
if (node.callee.name === "Date" && node.arguments.length > 0) {
|
|
15
|
+
context.report(
|
|
16
|
+
node,
|
|
17
|
+
"Use createDate() instead because Safari parses string dates differently"
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
});
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
const createRule = require("../utils");
|
|
2
|
+
|
|
3
|
+
const RE_REGEXP_STR = /^\/(.+)\/(.*)$/u;
|
|
4
|
+
|
|
5
|
+
const hasOnlyWhitespace = (value) => /^[\r\n\s\t\f\v]+$/.test(value);
|
|
6
|
+
|
|
7
|
+
function parseTargetAttrs(options) {
|
|
8
|
+
const regexps = [];
|
|
9
|
+
for (const tagName of Object.keys(options)) {
|
|
10
|
+
const attrs = new Set(options[tagName]);
|
|
11
|
+
regexps.push({
|
|
12
|
+
name: toRegExp(tagName),
|
|
13
|
+
attrs,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return regexps;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function toRegExp(str) {
|
|
20
|
+
const parts = RE_REGEXP_STR.exec(str);
|
|
21
|
+
if (parts) {
|
|
22
|
+
return new RegExp(parts[1], parts[2]);
|
|
23
|
+
}
|
|
24
|
+
return new RegExp(`^${encodeURI(str)}$`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getTargetAttrs(tagName, config) {
|
|
28
|
+
const result = [];
|
|
29
|
+
for (const { name, attrs } of config.attributes) {
|
|
30
|
+
name.lastIndex = 0;
|
|
31
|
+
if (name.test(tagName)) {
|
|
32
|
+
result.push(...attrs);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return new Set(result);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function checkSvelteLiteralOrText(context, literal, config) {
|
|
40
|
+
if (testValue(literal.value, config)) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const loc = literal.loc;
|
|
45
|
+
context.report({
|
|
46
|
+
loc,
|
|
47
|
+
message: `Raw text '${literal.value.trim()}' is used. Use {$T.Something()}`,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function testValue(value, config) {
|
|
52
|
+
if (typeof value === "string") {
|
|
53
|
+
return (
|
|
54
|
+
hasOnlyWhitespace(value) ||
|
|
55
|
+
config.ignorePattern.test(value.trim()) ||
|
|
56
|
+
config.ignoreText.includes(value.trim())
|
|
57
|
+
);
|
|
58
|
+
} else {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function checkSvelteMustacheTagText(context, node, config) {
|
|
64
|
+
if (!node.expression || !node.parent) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (node.parent.type === "SvelteElement") {
|
|
69
|
+
checkExpressionText(context, node.expression, config);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function checkExpressionText(context, expression, config) {
|
|
74
|
+
if (expression.type === "Literal") {
|
|
75
|
+
checkLiteral(context, expression, config);
|
|
76
|
+
} else if (isStaticTemplateLiteral(expression)) {
|
|
77
|
+
checkLiteral(context, expression, config);
|
|
78
|
+
} else if (expression.type === "ConditionalExpression") {
|
|
79
|
+
const targets = [expression.consequent, expression.alternate];
|
|
80
|
+
targets.forEach((target) => {
|
|
81
|
+
if (target.type === "Literal") {
|
|
82
|
+
checkLiteral(context, target, config);
|
|
83
|
+
} else if (isStaticTemplateLiteral(target)) {
|
|
84
|
+
checkLiteral(context, target, config);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function checkLiteral(context, literal, config) {
|
|
91
|
+
const value =
|
|
92
|
+
literal.type !== "TemplateLiteral"
|
|
93
|
+
? literal.value
|
|
94
|
+
: literal.quasis[0].value.cooked;
|
|
95
|
+
|
|
96
|
+
if (testValue(value, config)) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const loc = literal.loc;
|
|
101
|
+
context.report({
|
|
102
|
+
loc,
|
|
103
|
+
message: `Raw text '${value.trim()}' is used. Use {$T.Something()}`,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function isStaticTemplateLiteral(node) {
|
|
108
|
+
return Boolean(
|
|
109
|
+
node && node.type === "TemplateLiteral" && node.expressions.length === 0
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function create(context) {
|
|
114
|
+
const sourceCode = context.getSourceCode(),
|
|
115
|
+
config = {
|
|
116
|
+
attributes: [],
|
|
117
|
+
ignorePattern: /^$/,
|
|
118
|
+
ignoreNodes: [],
|
|
119
|
+
ignoreText: [],
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
if (context.options[0]?.ignorePattern) {
|
|
123
|
+
config.ignorePattern = new RegExp(context.options[0].ignorePattern, "u");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (context.options[0]?.ignoreNodes) {
|
|
127
|
+
config.ignoreNodes = context.options[0].ignoreNodes;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (context.options[0]?.ignoreText) {
|
|
131
|
+
config.ignoreText = context.options[0].ignoreText;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (context.options[0]?.attributes) {
|
|
135
|
+
config.attributes = parseTargetAttrs(context.options[0].attributes);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function isIgnore(node) {
|
|
139
|
+
const element = getElement(node);
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
!element ||
|
|
143
|
+
config.ignoreNodes.includes(sourceCode.text.slice(...element.name.range))
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function getElement(node) {
|
|
148
|
+
let target = node.parent;
|
|
149
|
+
while (
|
|
150
|
+
target.type === "SvelteIfBlock" ||
|
|
151
|
+
target.type === "SvelteElseBlock" ||
|
|
152
|
+
target.type === "SvelteEachBlock" ||
|
|
153
|
+
target.type === "SvelteAwaitBlock" ||
|
|
154
|
+
target.type === "SvelteAwaitPendingBlock" ||
|
|
155
|
+
target.type === "SvelteAwaitThenBlock" ||
|
|
156
|
+
target.type === "SvelteAwaitCatchBlock" ||
|
|
157
|
+
target.type === "SvelteKeyBlock"
|
|
158
|
+
) {
|
|
159
|
+
target = target.parent;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (target.type === "SvelteElement") {
|
|
163
|
+
return target;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
SvelteAttribute(node) {
|
|
171
|
+
if (node.value.length !== 1 || node.value[0].type !== "SvelteLiteral") {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const nameNode = node.parent.parent.name;
|
|
176
|
+
const tagName = sourceCode.text.slice(...nameNode.range);
|
|
177
|
+
const attrName = node.key.name;
|
|
178
|
+
|
|
179
|
+
if (!getTargetAttrs(tagName, config).has(attrName)) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
checkSvelteLiteralOrText(context, node.value[0], config);
|
|
184
|
+
},
|
|
185
|
+
SvelteMustacheTag(node) {
|
|
186
|
+
if (isIgnore(node)) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
checkSvelteMustacheTagText(context, node, config);
|
|
190
|
+
},
|
|
191
|
+
SvelteText(node) {
|
|
192
|
+
if (isIgnore(node)) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
checkSvelteLiteralOrText(context, node, config);
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
module.exports = createRule("no-raw-text", {
|
|
201
|
+
meta: {
|
|
202
|
+
docs: {
|
|
203
|
+
description: "Disallow string literals and enforce translatable strings",
|
|
204
|
+
},
|
|
205
|
+
recommended: false,
|
|
206
|
+
schema: [
|
|
207
|
+
{
|
|
208
|
+
type: "object",
|
|
209
|
+
properties: {
|
|
210
|
+
attributes: {
|
|
211
|
+
type: "object",
|
|
212
|
+
patternProperties: {
|
|
213
|
+
"^(?:\\S+|/.*/[a-z]*)$": {
|
|
214
|
+
type: "array",
|
|
215
|
+
items: { type: "string" },
|
|
216
|
+
uniqueItems: true,
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
additionalProperties: false,
|
|
220
|
+
},
|
|
221
|
+
ignoreNodes: {
|
|
222
|
+
type: "array",
|
|
223
|
+
},
|
|
224
|
+
ignorePattern: {
|
|
225
|
+
type: "string",
|
|
226
|
+
},
|
|
227
|
+
ignoreText: {
|
|
228
|
+
type: "array",
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
},
|
|
234
|
+
create,
|
|
235
|
+
});
|