@discourse/lint-configs 2.19.1 → 2.21.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
|
+
};
|
|
@@ -2,7 +2,7 @@ export default {
|
|
|
2
2
|
meta: {
|
|
3
3
|
type: "problem",
|
|
4
4
|
docs: {
|
|
5
|
-
description: "
|
|
5
|
+
description: "prevent using deprecated plugin APIs",
|
|
6
6
|
},
|
|
7
7
|
fixable: "code",
|
|
8
8
|
schema: [], // no options
|
|
@@ -23,6 +23,16 @@ export default {
|
|
|
23
23
|
message:
|
|
24
24
|
"registerConnectorClass is deprecated. Create a glimmer component in a plugin connector directory or use renderInOutlet instead.",
|
|
25
25
|
});
|
|
26
|
+
} else if (
|
|
27
|
+
callee.type === "MemberExpression" &&
|
|
28
|
+
callee.property.name === "decoratePluginOutlet" &&
|
|
29
|
+
args.length === 2
|
|
30
|
+
) {
|
|
31
|
+
context.report({
|
|
32
|
+
node,
|
|
33
|
+
message:
|
|
34
|
+
"decoratePluginOutlet is deprecated. Use element modifiers on a component instead.",
|
|
35
|
+
});
|
|
26
36
|
}
|
|
27
37
|
},
|
|
28
38
|
};
|
|
@@ -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,14 +12,16 @@ 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";
|
|
17
|
+
import deprecatedPluginApis from "./eslint-rules/deprecated-plugin-apis.mjs";
|
|
16
18
|
import discourseCommonImports from "./eslint-rules/discourse-common-imports.mjs";
|
|
17
19
|
import i18nImport from "./eslint-rules/i18n-import-location.mjs";
|
|
18
20
|
import i18nT from "./eslint-rules/i18n-t.mjs";
|
|
19
21
|
import lineAfterImports from "./eslint-rules/line-after-imports.mjs";
|
|
20
22
|
import lineBeforeDefaultExport from "./eslint-rules/line-before-default-export.mjs";
|
|
21
23
|
import linesBetweenClassMembers from "./eslint-rules/lines-between-class-members.mjs";
|
|
22
|
-
import
|
|
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";
|
|
@@ -119,9 +121,11 @@ export default [
|
|
|
119
121
|
"deprecated-lookups": deprecatedLookups,
|
|
120
122
|
"discourse-common-imports": discourseCommonImports,
|
|
121
123
|
"lines-between-class-members": linesBetweenClassMembers,
|
|
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
|
},
|
|
@@ -296,6 +300,8 @@ export default [
|
|
|
296
300
|
"discourse/lines-between-class-members": ["error"],
|
|
297
301
|
"discourse/line-after-imports": ["error"],
|
|
298
302
|
"discourse/line-before-default-export": ["error"],
|
|
303
|
+
"discourse/no-curly-components": ["error"],
|
|
304
|
+
"discourse/capital-components": ["error"],
|
|
299
305
|
},
|
|
300
306
|
},
|
|
301
307
|
{
|