@discourse/lint-configs 2.20.0 → 2.22.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.
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
meta: {
|
|
3
|
+
type: "suggestion",
|
|
4
|
+
docs: {
|
|
5
|
+
description: "Component names should start with a capital letter.",
|
|
6
|
+
},
|
|
7
|
+
fixable: "code",
|
|
8
|
+
schema: [], // no options
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
create(context) {
|
|
12
|
+
return {
|
|
13
|
+
GlimmerElementNode(node) {
|
|
14
|
+
if (node.name === "template") {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!node.name.match(/^[a-z]/)) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const moduleScope = context.sourceCode.scopeManager.scopes.find(
|
|
23
|
+
(s) => s.type === "module"
|
|
24
|
+
);
|
|
25
|
+
const variable = moduleScope.variables.find(
|
|
26
|
+
(v) => v.name === node.name
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
if (!variable) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const newVariableName = variable.name.replace(/^[a-z]/, (char) =>
|
|
34
|
+
char.toUpperCase()
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const importBinding = variable.defs.find(
|
|
38
|
+
(d) =>
|
|
39
|
+
d.type === "ImportBinding" &&
|
|
40
|
+
d.node.type === "ImportDefaultSpecifier"
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
if (!importBinding) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
context.report({
|
|
48
|
+
node: node.openTag,
|
|
49
|
+
message: `Component names should start with a capital letter.`,
|
|
50
|
+
fix(fixer) {
|
|
51
|
+
const fixes = [];
|
|
52
|
+
|
|
53
|
+
fixes.push(fixer.replaceText(importBinding.node, newVariableName));
|
|
54
|
+
|
|
55
|
+
variable.references.forEach((ref) => {
|
|
56
|
+
fixes.push(fixer.replaceText(ref.identifier, newVariableName));
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return fixes;
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
|
|
3
|
+
function lintCurlyComponent(node, context) {
|
|
4
|
+
const isSimplePath =
|
|
5
|
+
node.path.type === "GlimmerPathExpression" &&
|
|
6
|
+
node.path.head.type === "VarHead" &&
|
|
7
|
+
!node.tail?.length;
|
|
8
|
+
|
|
9
|
+
if (!isSimplePath) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let check = node.parent;
|
|
14
|
+
while (check) {
|
|
15
|
+
if (check.type === "GlimmerAttrNode") {
|
|
16
|
+
// <Foo @bar={{baz}} />
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
check = check.parent;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const variableName = node.path.head.name;
|
|
23
|
+
|
|
24
|
+
const moduleScope = context.sourceCode.scopeManager.scopes.find(
|
|
25
|
+
(s) => s.type === "module"
|
|
26
|
+
);
|
|
27
|
+
const variable = moduleScope.variables.find((v) => v.name === variableName);
|
|
28
|
+
|
|
29
|
+
if (!variable) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const importBinding = variable.defs.find(
|
|
34
|
+
(d) =>
|
|
35
|
+
d.type === "ImportBinding" && d.node.type === "ImportDefaultSpecifier"
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (!importBinding) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const importedModuleName = importBinding.node.parent.source.value;
|
|
43
|
+
|
|
44
|
+
// This is not perfect, but it should catch 99% of components
|
|
45
|
+
let resolvedModuleName = importedModuleName;
|
|
46
|
+
if (importedModuleName.startsWith(".")) {
|
|
47
|
+
const cwd = context.cwd;
|
|
48
|
+
const sourceDirectoryFromCwd = path.dirname(
|
|
49
|
+
path.relative(cwd, context.getFilename())
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
resolvedModuleName = path.join(sourceDirectoryFromCwd, importedModuleName);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!resolvedModuleName.includes("/components/")) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
context.report({
|
|
60
|
+
node,
|
|
61
|
+
message: `Use angle bracket syntax for components.`,
|
|
62
|
+
fix(fixer) {
|
|
63
|
+
const fixes = [];
|
|
64
|
+
|
|
65
|
+
let argumentString = "";
|
|
66
|
+
node.hash?.pairs.forEach(({ key, value }) => {
|
|
67
|
+
let valueSource = context.sourceCode.getText(value);
|
|
68
|
+
valueSource = valueSource.replace(/^\(/, "").replace(/\)$/, "");
|
|
69
|
+
if (value.type !== "GlimmerStringLiteral") {
|
|
70
|
+
valueSource = `{{${valueSource}}}`;
|
|
71
|
+
}
|
|
72
|
+
argumentString += `@${key}=${valueSource} `;
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (node.type === "GlimmerBlockStatement") {
|
|
76
|
+
fixes.push(
|
|
77
|
+
fixer.replaceText(
|
|
78
|
+
node,
|
|
79
|
+
`<${variable.name} ${argumentString}>${context.sourceCode.getText(
|
|
80
|
+
node.program
|
|
81
|
+
)}</${variable.name}>`
|
|
82
|
+
)
|
|
83
|
+
);
|
|
84
|
+
} else if (node.type === "GlimmerMustacheStatement") {
|
|
85
|
+
fixes.push(
|
|
86
|
+
fixer.replaceText(node, `<${variable.name} ${argumentString}/>`)
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return fixes;
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export default {
|
|
96
|
+
meta: {
|
|
97
|
+
type: "suggestion",
|
|
98
|
+
docs: {
|
|
99
|
+
description: "Use angle-bracket syntax for components.",
|
|
100
|
+
},
|
|
101
|
+
fixable: "code",
|
|
102
|
+
schema: [], // no options
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
create(context) {
|
|
106
|
+
return {
|
|
107
|
+
GlimmerBlockStatement(node) {
|
|
108
|
+
return lintCurlyComponent(node, context);
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
GlimmerMustacheStatement(node) {
|
|
112
|
+
return lintCurlyComponent(node, context);
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
},
|
|
116
|
+
};
|
package/eslint.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import QUnitRecommended from "eslint-plugin-qunit/configs/recommended";
|
|
|
12
12
|
import SimpleImportSort from "eslint-plugin-simple-import-sort";
|
|
13
13
|
import SortClassMembers from "eslint-plugin-sort-class-members";
|
|
14
14
|
import globals from "globals";
|
|
15
|
+
import capitalComponents from "./eslint-rules/capital-components.mjs";
|
|
15
16
|
import deprecatedLookups from "./eslint-rules/deprecated-lookups.mjs";
|
|
16
17
|
import deprecatedPluginApis from "./eslint-rules/deprecated-plugin-apis.mjs";
|
|
17
18
|
import discourseCommonImports from "./eslint-rules/discourse-common-imports.mjs";
|
|
@@ -20,6 +21,7 @@ import i18nT from "./eslint-rules/i18n-t.mjs";
|
|
|
20
21
|
import lineAfterImports from "./eslint-rules/line-after-imports.mjs";
|
|
21
22
|
import lineBeforeDefaultExport from "./eslint-rules/line-before-default-export.mjs";
|
|
22
23
|
import linesBetweenClassMembers from "./eslint-rules/lines-between-class-members.mjs";
|
|
24
|
+
import noCurlyComponents from "./eslint-rules/no-curly-components.mjs";
|
|
23
25
|
import noSimpleQuerySelector from "./eslint-rules/no-simple-query-selector.mjs";
|
|
24
26
|
import serviceInjectImport from "./eslint-rules/service-inject-import.mjs";
|
|
25
27
|
import truthHelpersImports from "./eslint-rules/truth-helpers-imports.mjs";
|
|
@@ -122,6 +124,8 @@ export default [
|
|
|
122
124
|
"deprecated-plugin-apis": deprecatedPluginApis,
|
|
123
125
|
"line-after-imports": lineAfterImports,
|
|
124
126
|
"line-before-default-export": lineBeforeDefaultExport,
|
|
127
|
+
"no-curly-components": noCurlyComponents,
|
|
128
|
+
"capital-components": capitalComponents,
|
|
125
129
|
},
|
|
126
130
|
},
|
|
127
131
|
},
|
|
@@ -186,8 +190,6 @@ export default [
|
|
|
186
190
|
"ember/avoid-leaking-state-in-ember-objects": "off",
|
|
187
191
|
"ember/no-get": "off",
|
|
188
192
|
"ember/no-observers": "off",
|
|
189
|
-
"ember/no-mixins": "off",
|
|
190
|
-
"ember/no-new-mixins": "off",
|
|
191
193
|
"ember/no-implicit-injections": "off", // this rule is broken
|
|
192
194
|
"ember/no-array-prototype-extensions": "off",
|
|
193
195
|
"ember/no-at-ember-render-modifiers": "off",
|
|
@@ -296,6 +298,8 @@ export default [
|
|
|
296
298
|
"discourse/lines-between-class-members": ["error"],
|
|
297
299
|
"discourse/line-after-imports": ["error"],
|
|
298
300
|
"discourse/line-before-default-export": ["error"],
|
|
301
|
+
"discourse/no-curly-components": ["error"],
|
|
302
|
+
"discourse/capital-components": ["error"],
|
|
299
303
|
},
|
|
300
304
|
},
|
|
301
305
|
{
|