@discourse/lint-configs 2.1.0 → 2.2.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.
|
@@ -62,7 +62,7 @@ export function fixImport(
|
|
|
62
62
|
if (finalNamedImports.length > 0) {
|
|
63
63
|
newImportStatement += `{ ${finalNamedImports.join(", ")} }`;
|
|
64
64
|
}
|
|
65
|
-
newImportStatement += ` from
|
|
65
|
+
newImportStatement += ` from "${importDeclarationNode.source.value}";`;
|
|
66
66
|
|
|
67
67
|
// Replace the entire import declaration
|
|
68
68
|
return fixer.replaceText(importDeclarationNode, newImportStatement);
|
package/eslint.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import BabelParser from "@babel/eslint-parser";
|
|
2
2
|
import js from "@eslint/js";
|
|
3
|
+
import stylisticJs from "@stylistic/eslint-plugin-js";
|
|
3
4
|
import EmberESLintParser from "ember-eslint-parser";
|
|
4
5
|
import DecoratorPosition from "eslint-plugin-decorator-position";
|
|
5
6
|
import EmberPlugin from "eslint-plugin-ember";
|
|
@@ -85,6 +86,7 @@ export default [
|
|
|
85
86
|
},
|
|
86
87
|
},
|
|
87
88
|
plugins: {
|
|
89
|
+
"@stylistic/js": stylisticJs,
|
|
88
90
|
ember: EmberPlugin,
|
|
89
91
|
"sort-class-members": SortClassMembers,
|
|
90
92
|
"decorator-position": DecoratorPosition,
|
|
@@ -147,6 +149,16 @@ export default [
|
|
|
147
149
|
"no-duplicate-imports": "error",
|
|
148
150
|
"object-shorthand": ["error", "properties"],
|
|
149
151
|
"no-dupe-class-members": "error",
|
|
152
|
+
"@stylistic/js/lines-between-class-members": [
|
|
153
|
+
"error",
|
|
154
|
+
{
|
|
155
|
+
enforce: [
|
|
156
|
+
{ blankLine: "always", prev: "*", next: "method" },
|
|
157
|
+
{ blankLine: "always", prev: "method", next: "*" },
|
|
158
|
+
],
|
|
159
|
+
},
|
|
160
|
+
{ exceptAfterSingleLine: true },
|
|
161
|
+
],
|
|
150
162
|
"ember/no-classic-components": "off",
|
|
151
163
|
"ember/no-component-lifecycle-hooks": "off",
|
|
152
164
|
"ember/require-tagless-components": "off",
|
|
@@ -200,6 +212,8 @@ export default [
|
|
|
200
212
|
"[properties]",
|
|
201
213
|
"[private-properties]",
|
|
202
214
|
"constructor",
|
|
215
|
+
"init",
|
|
216
|
+
"willDestroy",
|
|
203
217
|
"[everything-else]",
|
|
204
218
|
"[template-tag]",
|
|
205
219
|
],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@discourse/lint-configs",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Shareable lint configs for Discourse core, plugins, and themes",
|
|
5
5
|
"author": "Discourse",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"@babel/core": "^7.25.8",
|
|
33
33
|
"@babel/eslint-parser": "^7.25.8",
|
|
34
34
|
"@babel/plugin-proposal-decorators": "^7.25.7",
|
|
35
|
+
"@stylistic/eslint-plugin-js": "^2.11.0",
|
|
35
36
|
"ember-template-lint": "^6.0.0",
|
|
36
37
|
"eslint": "^9.14.0",
|
|
37
38
|
"eslint-plugin-decorator-position": "^6.0.0",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import NoAtClass from "./no-at-class.mjs";
|
|
2
|
+
import NoImplicitThis from "./no-implicit-this.mjs";
|
|
2
3
|
|
|
3
4
|
export default {
|
|
4
5
|
// Name of plugin
|
|
@@ -7,5 +8,6 @@ export default {
|
|
|
7
8
|
// Define rules for this plugin. Each path should map to a plugin rule
|
|
8
9
|
rules: {
|
|
9
10
|
"discourse/no-at-class": NoAtClass,
|
|
11
|
+
"discourse/no-implicit-this": NoImplicitThis,
|
|
10
12
|
},
|
|
11
13
|
};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// Adapted from https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-implicit-this.md
|
|
2
|
+
// With the addition of autofix
|
|
3
|
+
|
|
4
|
+
import { Rule } from "ember-template-lint";
|
|
5
|
+
|
|
6
|
+
function createErrorMessage(ruleName, lines, config) {
|
|
7
|
+
return [
|
|
8
|
+
`The ${ruleName} rule accepts one of the following values.`,
|
|
9
|
+
lines,
|
|
10
|
+
`You specified \`${JSON.stringify(config)}\``,
|
|
11
|
+
].join("\n");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function message(original) {
|
|
15
|
+
return (
|
|
16
|
+
`Ambiguous path '${original}' is not allowed. ` +
|
|
17
|
+
`Use '@${original}' if it is a named argument ` +
|
|
18
|
+
`or 'this.${original}' if it is a property on 'this'. ` +
|
|
19
|
+
"If it is a helper or component that has no arguments, " +
|
|
20
|
+
"you must either convert it to an angle bracket invocation " +
|
|
21
|
+
"or manually add it to the 'no-implicit-this' rule configuration, e.g. " +
|
|
22
|
+
`'no-implicit-this': { allow: ['${original}'] }.`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isString(value) {
|
|
27
|
+
return typeof value === "string";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isRegExp(value) {
|
|
31
|
+
return value instanceof RegExp;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function allowedFormat(value) {
|
|
35
|
+
return isString(value) || isRegExp(value);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Allow Ember's builtin argless syntaxes
|
|
39
|
+
export const ARGLESS_BUILTIN_HELPERS = [
|
|
40
|
+
"array",
|
|
41
|
+
"concat",
|
|
42
|
+
"debugger",
|
|
43
|
+
"has-block",
|
|
44
|
+
"hasBlock",
|
|
45
|
+
"has-block-params",
|
|
46
|
+
"hasBlockParams",
|
|
47
|
+
"hash",
|
|
48
|
+
"input",
|
|
49
|
+
"log",
|
|
50
|
+
"outlet",
|
|
51
|
+
"query-params",
|
|
52
|
+
"textarea",
|
|
53
|
+
"yield",
|
|
54
|
+
"unique-id",
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
// arg'less Components / Helpers in default ember-cli blueprint
|
|
58
|
+
const ARGLESS_DEFAULT_BLUEPRINT = [
|
|
59
|
+
"welcome-page",
|
|
60
|
+
/* from app/index.html and tests/index.html */
|
|
61
|
+
"rootURL",
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
export default class NoImplicitThis extends Rule {
|
|
65
|
+
parseConfig(config) {
|
|
66
|
+
if (config === false || config === undefined || !this.isStrictMode) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
switch (typeof config) {
|
|
71
|
+
case "undefined": {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
case "boolean": {
|
|
76
|
+
if (config) {
|
|
77
|
+
return {
|
|
78
|
+
allow: [...ARGLESS_BUILTIN_HELPERS, ...ARGLESS_DEFAULT_BLUEPRINT],
|
|
79
|
+
};
|
|
80
|
+
} else {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
case "object": {
|
|
86
|
+
if (Array.isArray(config.allow) && config.allow.every(allowedFormat)) {
|
|
87
|
+
return {
|
|
88
|
+
allow: [
|
|
89
|
+
...ARGLESS_BUILTIN_HELPERS,
|
|
90
|
+
...ARGLESS_DEFAULT_BLUEPRINT,
|
|
91
|
+
...config.allow,
|
|
92
|
+
],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let errorMessage = createErrorMessage(
|
|
100
|
+
this.ruleName,
|
|
101
|
+
[
|
|
102
|
+
" * boolean - `true` to enable / `false` to disable",
|
|
103
|
+
" * object -- An object with the following keys:",
|
|
104
|
+
" * `allow` -- An array of component / helper names for that may be called without arguments",
|
|
105
|
+
],
|
|
106
|
+
config
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
throw new Error(errorMessage);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// The way this visitor works is a bit sketchy. We need to lint the PathExpressions
|
|
113
|
+
// in the callee position differently those in an argument position.
|
|
114
|
+
//
|
|
115
|
+
// Unfortunately, the current visitor API doesn't give us a good way to differentiate
|
|
116
|
+
// these two cases. Instead, we rely on the fact that the _first_ PathExpression that
|
|
117
|
+
// we enter after entering a MustacheStatement/BlockStatement/... will be the callee
|
|
118
|
+
// and we track this using a flag called `nextPathIsCallee`.
|
|
119
|
+
visitor() {
|
|
120
|
+
let nextPathIsCallee = false;
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
PathExpression(path) {
|
|
124
|
+
if (nextPathIsCallee) {
|
|
125
|
+
// All paths are valid callees so there's nothing to check.
|
|
126
|
+
} else {
|
|
127
|
+
let valid =
|
|
128
|
+
path.data ||
|
|
129
|
+
path.this ||
|
|
130
|
+
this.isLocal(path) ||
|
|
131
|
+
this.config.allow.some((item) => {
|
|
132
|
+
return isRegExp(item)
|
|
133
|
+
? item.test(path.original)
|
|
134
|
+
: item === path.original;
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
if (!valid) {
|
|
138
|
+
if (this.mode === "fix") {
|
|
139
|
+
path.original = `this.${path.original}`;
|
|
140
|
+
} else {
|
|
141
|
+
this.log({
|
|
142
|
+
message: message(path.original),
|
|
143
|
+
node: path,
|
|
144
|
+
isFixable: true,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
nextPathIsCallee = false;
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
SubExpression() {
|
|
154
|
+
nextPathIsCallee = true;
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
ElementModifierStatement() {
|
|
158
|
+
nextPathIsCallee = true;
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
MustacheStatement(node) {
|
|
162
|
+
let isCall = node.params.length > 0 || node.hash.pairs.length > 0;
|
|
163
|
+
|
|
164
|
+
nextPathIsCallee = isCall;
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
BlockStatement: {
|
|
168
|
+
enter() {
|
|
169
|
+
nextPathIsCallee = true;
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
package/template-lint.config.cjs
CHANGED
|
@@ -46,5 +46,20 @@ module.exports = {
|
|
|
46
46
|
|
|
47
47
|
// Discourse custom
|
|
48
48
|
"discourse/no-at-class": true,
|
|
49
|
+
"discourse/no-implicit-this": {
|
|
50
|
+
allow: [
|
|
51
|
+
"hide-application-footer",
|
|
52
|
+
"hide-application-sidebar",
|
|
53
|
+
"loading-spinner",
|
|
54
|
+
],
|
|
55
|
+
},
|
|
49
56
|
},
|
|
57
|
+
overrides: [
|
|
58
|
+
{
|
|
59
|
+
files: ["**/*.gjs", "**/*.gts"],
|
|
60
|
+
rules: {
|
|
61
|
+
"discourse/no-implicit-this": false,
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
],
|
|
50
65
|
};
|