@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: "disallow api.registerConnectorClass() uses",
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 noRegisterConnectorClass from "./eslint-rules/no-register-connector-class.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";
@@ -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
- "no-register-connector-class": noRegisterConnectorClass,
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
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@discourse/lint-configs",
3
- "version": "2.19.1",
3
+ "version": "2.21.0",
4
4
  "description": "Shareable lint configs for Discourse core, plugins, and themes",
5
5
  "author": "Discourse",
6
6
  "license": "MIT",