@discourse/lint-configs 2.44.1 → 2.45.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/eslint-rules/i18n-import-location.mjs +9 -9
- package/eslint-rules/no-computed-macros/computed-macros-analysis.mjs +903 -0
- package/eslint-rules/no-computed-macros/computed-macros-fixer.mjs +612 -0
- package/eslint-rules/no-computed-macros/macro-transforms.mjs +645 -0
- package/eslint-rules/no-computed-macros.mjs +436 -0
- package/eslint-rules/no-discourse-computed.mjs +21 -21
- package/eslint-rules/truth-helpers-imports.mjs +9 -5
- package/eslint-rules/utils/fix-import.mjs +32 -12
- package/eslint.mjs +4 -1
- package/package.json +1 -1
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Transform registry for computed property macros.
|
|
3
|
+
*
|
|
4
|
+
* Maps each macro name to its source, whether it can be auto-fixed,
|
|
5
|
+
* what additional imports the transformation requires, how to generate
|
|
6
|
+
* the getter body, and how to derive the dependent keys for @computed.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { propertyPathToOptionalChaining } from "../utils/property-path.mjs";
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Helpers
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Convert a property-path string to a `this.`-prefixed accessor.
|
|
17
|
+
* Delegates to the shared utility from `utils/property-path.mjs`.
|
|
18
|
+
*
|
|
19
|
+
* @param {string} path
|
|
20
|
+
* @returns {string}
|
|
21
|
+
*/
|
|
22
|
+
function toAccess(path) {
|
|
23
|
+
return propertyPathToOptionalChaining(path, true, false);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Render a JS literal value suitable for source output.
|
|
28
|
+
* Strings → quoted, numbers/booleans → as-is, null → "null".
|
|
29
|
+
*
|
|
30
|
+
* @param {*} value
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
function renderLiteral(value) {
|
|
34
|
+
if (typeof value === "string") {
|
|
35
|
+
return JSON.stringify(value);
|
|
36
|
+
}
|
|
37
|
+
return String(value);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Parse an Ember-style format string (using `%@` / `%@N` placeholders)
|
|
42
|
+
* and return a JS template-literal expression.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} format - the format string, e.g. "/admin/users/%@1/%@2"
|
|
45
|
+
* @param {string[]} propPaths - the property-path arguments preceding the format string
|
|
46
|
+
* @returns {string} a template literal expression (with backticks)
|
|
47
|
+
*/
|
|
48
|
+
function fmtToTemplateLiteral(format, propPaths) {
|
|
49
|
+
let seqIdx = 0;
|
|
50
|
+
const result = format.replace(/%@(\d+)?/g, (_match, indexStr) => {
|
|
51
|
+
const idx = indexStr ? parseInt(indexStr, 10) - 1 : seqIdx++;
|
|
52
|
+
const path = propPaths[idx];
|
|
53
|
+
if (!path) {
|
|
54
|
+
return "";
|
|
55
|
+
}
|
|
56
|
+
return `\${${toAccess(path)}}`;
|
|
57
|
+
});
|
|
58
|
+
return `\`${result}\``;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Check whether a dependent-key string is "local" (no dots, no special
|
|
63
|
+
* tokens like `@each` or `[]`).
|
|
64
|
+
*
|
|
65
|
+
* @param {string} key
|
|
66
|
+
* @returns {boolean}
|
|
67
|
+
*/
|
|
68
|
+
export function isLocalKey(key) {
|
|
69
|
+
return !key.includes(".");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Individual transforms
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
/** @typedef {{ name: string, source: string, isDefault?: boolean }} RequiredImport */
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @typedef {Object} MacroTransform
|
|
80
|
+
* @property {string} source - import source where the macro lives
|
|
81
|
+
* @property {boolean} canAutoFix
|
|
82
|
+
* @property {string} [reason] - explanation when canAutoFix is false
|
|
83
|
+
* @property {number} [depKeyArgCount] - how many leading args must be string
|
|
84
|
+
* literals (dep key paths); remaining args are "value args" that can be
|
|
85
|
+
* non-literal (their source text is used verbatim in the getter body)
|
|
86
|
+
* @property {RequiredImport[]} [requiredImports]
|
|
87
|
+
* @property {RequiredImport[]} [setterRequiredImports] - extra imports needed
|
|
88
|
+
* only when the setter uses Ember's `set()` (the `!allLocal` / `@computed` path)
|
|
89
|
+
* @property {(args: TransformArgs) => string} [toGetterBody]
|
|
90
|
+
* @property {(args: SetterTransformArgs) => string} [toSetterBody]
|
|
91
|
+
* @property {(args: TransformArgs) => string[]} [toDependentKeys]
|
|
92
|
+
*/
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @typedef {Object} TransformArgs
|
|
96
|
+
* @property {string[]} literalArgs - the literal string/number values of the decorator arguments
|
|
97
|
+
* @property {import('estree').Node[]} argNodes - raw AST argument nodes
|
|
98
|
+
* @property {string} propName - the decorated property name
|
|
99
|
+
* @property {import('eslint').SourceCode} sourceCode
|
|
100
|
+
*/
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @typedef {TransformArgs & { useEmberSet: boolean }} SetterTransformArgs
|
|
104
|
+
* `useEmberSet` is true when the `@computed` path requires Ember's `set()`,
|
|
105
|
+
* false when the `@dependentKeyCompat` path uses native assignment.
|
|
106
|
+
*/
|
|
107
|
+
|
|
108
|
+
const EMBER_SOURCE = "@ember/object/computed";
|
|
109
|
+
const DISCOURSE_SOURCE = "discourse/lib/computed";
|
|
110
|
+
|
|
111
|
+
// ---- @ember/object/computed ------------------------------------------------
|
|
112
|
+
|
|
113
|
+
const simpleAccess = {
|
|
114
|
+
source: EMBER_SOURCE,
|
|
115
|
+
canAutoFix: true,
|
|
116
|
+
toGetterBody({ literalArgs: [path] }) {
|
|
117
|
+
return `return ${toAccess(path)};`;
|
|
118
|
+
},
|
|
119
|
+
toDependentKeys({ literalArgs: [path] }) {
|
|
120
|
+
return [path];
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const alias = {
|
|
125
|
+
...simpleAccess,
|
|
126
|
+
setterRequiredImports: [{ name: "set", source: "@ember/object" }],
|
|
127
|
+
toSetterBody({ literalArgs: [path], useEmberSet }) {
|
|
128
|
+
if (useEmberSet) {
|
|
129
|
+
return `set(this, ${JSON.stringify(path)}, value);`;
|
|
130
|
+
}
|
|
131
|
+
return `this.${path} = value;`;
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
const readOnly = { ...simpleAccess };
|
|
135
|
+
|
|
136
|
+
// oneWay/reads: one-way binding that can be overridden locally.
|
|
137
|
+
// Getter reads from source until the setter is called, then the property
|
|
138
|
+
// permanently diverges. Uses a single `@tracked _propOverride` field (placed
|
|
139
|
+
// in the [private-properties] section via the `_` prefix). Checking against
|
|
140
|
+
// `undefined` distinguishes "never set" from "set to a value".
|
|
141
|
+
const oneWayTransform = {
|
|
142
|
+
source: EMBER_SOURCE,
|
|
143
|
+
canAutoFix: true,
|
|
144
|
+
overrideTrackedFields({ propName }) {
|
|
145
|
+
return [{ name: `_${propName}Override` }];
|
|
146
|
+
},
|
|
147
|
+
toGetterBody({ literalArgs: [path], propName }) {
|
|
148
|
+
return [
|
|
149
|
+
`if (this._${propName}Override !== undefined) {`,
|
|
150
|
+
` return this._${propName}Override;`,
|
|
151
|
+
`}`,
|
|
152
|
+
`return ${toAccess(path)};`,
|
|
153
|
+
].join("\n");
|
|
154
|
+
},
|
|
155
|
+
toSetterBody({ propName }) {
|
|
156
|
+
return `this._${propName}Override = value;`;
|
|
157
|
+
},
|
|
158
|
+
toDependentKeys({ literalArgs: [path] }) {
|
|
159
|
+
return [path];
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
const reads = { ...oneWayTransform };
|
|
163
|
+
const oneWay = { ...oneWayTransform };
|
|
164
|
+
|
|
165
|
+
const not = {
|
|
166
|
+
source: EMBER_SOURCE,
|
|
167
|
+
canAutoFix: true,
|
|
168
|
+
toGetterBody({ literalArgs: [path] }) {
|
|
169
|
+
return `return !${toAccess(path)};`;
|
|
170
|
+
},
|
|
171
|
+
toDependentKeys({ literalArgs: [path] }) {
|
|
172
|
+
return [path];
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const bool = {
|
|
177
|
+
source: EMBER_SOURCE,
|
|
178
|
+
canAutoFix: true,
|
|
179
|
+
toGetterBody({ literalArgs: [path] }) {
|
|
180
|
+
return `return !!${toAccess(path)};`;
|
|
181
|
+
},
|
|
182
|
+
toDependentKeys({ literalArgs: [path] }) {
|
|
183
|
+
return [path];
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const and = {
|
|
188
|
+
source: EMBER_SOURCE,
|
|
189
|
+
canAutoFix: true,
|
|
190
|
+
toGetterBody({ literalArgs }) {
|
|
191
|
+
return `return ${literalArgs.map(toAccess).join(" && ")};`;
|
|
192
|
+
},
|
|
193
|
+
toDependentKeys({ literalArgs }) {
|
|
194
|
+
return [...literalArgs];
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const or = {
|
|
199
|
+
source: EMBER_SOURCE,
|
|
200
|
+
canAutoFix: true,
|
|
201
|
+
toGetterBody({ literalArgs }) {
|
|
202
|
+
return `return ${literalArgs.map(toAccess).join(" || ")};`;
|
|
203
|
+
},
|
|
204
|
+
toDependentKeys({ literalArgs }) {
|
|
205
|
+
return [...literalArgs];
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
function comparisonMacro(operator) {
|
|
210
|
+
return {
|
|
211
|
+
source: EMBER_SOURCE,
|
|
212
|
+
canAutoFix: true,
|
|
213
|
+
depKeyArgCount: 1,
|
|
214
|
+
toGetterBody({ literalArgs: [path], argNodes, sourceCode }) {
|
|
215
|
+
const valueText = sourceCode.getText(argNodes[1]);
|
|
216
|
+
return `return ${toAccess(path)} ${operator} ${valueText};`;
|
|
217
|
+
},
|
|
218
|
+
toDependentKeys({ literalArgs: [path] }) {
|
|
219
|
+
return [path];
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const equal = comparisonMacro("===");
|
|
225
|
+
const gt = comparisonMacro(">");
|
|
226
|
+
const gte = comparisonMacro(">=");
|
|
227
|
+
const lt = comparisonMacro("<");
|
|
228
|
+
const lte = comparisonMacro("<=");
|
|
229
|
+
|
|
230
|
+
const notEmpty = {
|
|
231
|
+
source: EMBER_SOURCE,
|
|
232
|
+
canAutoFix: true,
|
|
233
|
+
requiredImports: [{ name: "isEmpty", source: "@ember/utils" }],
|
|
234
|
+
toGetterBody({ literalArgs: [path] }) {
|
|
235
|
+
return `return !isEmpty(${toAccess(path)});`;
|
|
236
|
+
},
|
|
237
|
+
toDependentKeys({ literalArgs: [path] }) {
|
|
238
|
+
return [`${path}.length`];
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const empty = {
|
|
243
|
+
source: EMBER_SOURCE,
|
|
244
|
+
canAutoFix: true,
|
|
245
|
+
requiredImports: [{ name: "isEmpty", source: "@ember/utils" }],
|
|
246
|
+
toGetterBody({ literalArgs: [path] }) {
|
|
247
|
+
return `return isEmpty(${toAccess(path)});`;
|
|
248
|
+
},
|
|
249
|
+
toDependentKeys({ literalArgs: [path] }) {
|
|
250
|
+
return [`${path}.length`];
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const none = {
|
|
255
|
+
source: EMBER_SOURCE,
|
|
256
|
+
canAutoFix: true,
|
|
257
|
+
toGetterBody({ literalArgs: [path] }) {
|
|
258
|
+
return `return ${toAccess(path)} == null;`;
|
|
259
|
+
},
|
|
260
|
+
toDependentKeys({ literalArgs: [path] }) {
|
|
261
|
+
return [path];
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const match = {
|
|
266
|
+
source: EMBER_SOURCE,
|
|
267
|
+
canAutoFix: true,
|
|
268
|
+
toGetterBody({ literalArgs: [path], argNodes, sourceCode }) {
|
|
269
|
+
const regexText = sourceCode.getText(argNodes[1]);
|
|
270
|
+
return `return ${regexText}.test(${toAccess(path)});`;
|
|
271
|
+
},
|
|
272
|
+
toDependentKeys({ literalArgs: [path] }) {
|
|
273
|
+
return [path];
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const mapBy = {
|
|
278
|
+
source: EMBER_SOURCE,
|
|
279
|
+
canAutoFix: true,
|
|
280
|
+
toGetterBody({ literalArgs: [arrPath, prop] }) {
|
|
281
|
+
return `return ${toAccess(arrPath)}?.map?.((item) => item.${prop}) ?? [];`;
|
|
282
|
+
},
|
|
283
|
+
toDependentKeys({ literalArgs: [arrPath, prop] }) {
|
|
284
|
+
return [`${arrPath}.@each.${prop}`];
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const filterBy = {
|
|
289
|
+
source: EMBER_SOURCE,
|
|
290
|
+
canAutoFix: true,
|
|
291
|
+
toGetterBody({ literalArgs, argNodes }) {
|
|
292
|
+
const arrPath = literalArgs[0];
|
|
293
|
+
const prop = literalArgs[1];
|
|
294
|
+
if (argNodes.length === 3) {
|
|
295
|
+
const valueText =
|
|
296
|
+
argNodes[2].raw !== undefined
|
|
297
|
+
? argNodes[2].raw
|
|
298
|
+
: renderLiteral(argNodes[2].value);
|
|
299
|
+
return `return ${toAccess(arrPath)}?.filter?.((item) => item.${prop} === ${valueText}) ?? [];`;
|
|
300
|
+
}
|
|
301
|
+
return `return ${toAccess(arrPath)}?.filter?.((item) => item.${prop}) ?? [];`;
|
|
302
|
+
},
|
|
303
|
+
toDependentKeys({ literalArgs: [arrPath, prop] }) {
|
|
304
|
+
return [`${arrPath}.@each.${prop}`];
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const collect = {
|
|
309
|
+
source: EMBER_SOURCE,
|
|
310
|
+
canAutoFix: true,
|
|
311
|
+
toGetterBody({ literalArgs }) {
|
|
312
|
+
const items = literalArgs
|
|
313
|
+
.map((p) => {
|
|
314
|
+
const access = toAccess(p);
|
|
315
|
+
return `${access} === undefined ? null : ${access}`;
|
|
316
|
+
})
|
|
317
|
+
.join(", ");
|
|
318
|
+
return `return [${items}];`;
|
|
319
|
+
},
|
|
320
|
+
toDependentKeys({ literalArgs }) {
|
|
321
|
+
return [...literalArgs];
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
const sum = {
|
|
326
|
+
source: EMBER_SOURCE,
|
|
327
|
+
canAutoFix: true,
|
|
328
|
+
toGetterBody({ literalArgs: [path] }) {
|
|
329
|
+
return `return ${toAccess(path)}?.reduce?.((s, v) => s + v, 0) ?? 0;`;
|
|
330
|
+
},
|
|
331
|
+
toDependentKeys({ literalArgs: [path] }) {
|
|
332
|
+
return [path];
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const max = {
|
|
337
|
+
source: EMBER_SOURCE,
|
|
338
|
+
canAutoFix: true,
|
|
339
|
+
toGetterBody({ literalArgs: [path] }) {
|
|
340
|
+
return `return ${toAccess(path)}?.reduce?.((m, v) => Math.max(m, v), -Infinity) ?? -Infinity;`;
|
|
341
|
+
},
|
|
342
|
+
toDependentKeys({ literalArgs: [path] }) {
|
|
343
|
+
return [path];
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const min = {
|
|
348
|
+
source: EMBER_SOURCE,
|
|
349
|
+
canAutoFix: true,
|
|
350
|
+
toGetterBody({ literalArgs: [path] }) {
|
|
351
|
+
return `return ${toAccess(path)}?.reduce?.((m, v) => Math.min(m, v), Infinity) ?? Infinity;`;
|
|
352
|
+
},
|
|
353
|
+
toDependentKeys({ literalArgs: [path] }) {
|
|
354
|
+
return [path];
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const uniq = {
|
|
359
|
+
source: EMBER_SOURCE,
|
|
360
|
+
canAutoFix: true,
|
|
361
|
+
requiredImports: [
|
|
362
|
+
{ name: "uniqueItemsFromArray", source: "discourse/lib/array-tools" },
|
|
363
|
+
],
|
|
364
|
+
toGetterBody({ literalArgs }) {
|
|
365
|
+
if (literalArgs.length === 1) {
|
|
366
|
+
return `return uniqueItemsFromArray(${toAccess(literalArgs[0])} ?? []);`;
|
|
367
|
+
}
|
|
368
|
+
const spreads = literalArgs
|
|
369
|
+
.map((p) => `...(${toAccess(p)} ?? [])`)
|
|
370
|
+
.join(", ");
|
|
371
|
+
return `return uniqueItemsFromArray([${spreads}]);`;
|
|
372
|
+
},
|
|
373
|
+
toDependentKeys({ literalArgs }) {
|
|
374
|
+
return [...literalArgs];
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const uniqBy = {
|
|
379
|
+
source: EMBER_SOURCE,
|
|
380
|
+
canAutoFix: true,
|
|
381
|
+
requiredImports: [
|
|
382
|
+
{ name: "uniqueItemsFromArray", source: "discourse/lib/array-tools" },
|
|
383
|
+
],
|
|
384
|
+
toGetterBody({ literalArgs: [arrPath, key] }) {
|
|
385
|
+
return `return uniqueItemsFromArray(${toAccess(arrPath)} ?? [], ${renderLiteral(key)});`;
|
|
386
|
+
},
|
|
387
|
+
toDependentKeys({ literalArgs: [arrPath] }) {
|
|
388
|
+
return [arrPath];
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// union === uniq (same function in Ember)
|
|
393
|
+
const union = { ...uniq };
|
|
394
|
+
|
|
395
|
+
const intersect = {
|
|
396
|
+
source: EMBER_SOURCE,
|
|
397
|
+
canAutoFix: true,
|
|
398
|
+
toGetterBody({ literalArgs }) {
|
|
399
|
+
// Ember filters from the LAST array against all others
|
|
400
|
+
const last = literalArgs[literalArgs.length - 1];
|
|
401
|
+
const rest = literalArgs.slice(0, -1);
|
|
402
|
+
const conditions = rest
|
|
403
|
+
.map((p) => `${toAccess(p)}?.includes?.(item)`)
|
|
404
|
+
.join(" && ");
|
|
405
|
+
return `return ${toAccess(last)}?.filter?.((item) => ${conditions}) ?? [];`;
|
|
406
|
+
},
|
|
407
|
+
toDependentKeys({ literalArgs }) {
|
|
408
|
+
return [...literalArgs];
|
|
409
|
+
},
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const setDiff = {
|
|
413
|
+
source: EMBER_SOURCE,
|
|
414
|
+
canAutoFix: true,
|
|
415
|
+
toGetterBody({ literalArgs: [a, b] }) {
|
|
416
|
+
return `return ${toAccess(a)}?.filter?.((item) => !${toAccess(b)}?.includes?.(item)) ?? [];`;
|
|
417
|
+
},
|
|
418
|
+
toDependentKeys({ literalArgs: [a, b] }) {
|
|
419
|
+
return [a, b];
|
|
420
|
+
},
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
const sort = {
|
|
424
|
+
source: EMBER_SOURCE,
|
|
425
|
+
canAutoFix: true,
|
|
426
|
+
requiredImports: [
|
|
427
|
+
{ name: "arraySortedByProperties", source: "discourse/lib/array-tools" },
|
|
428
|
+
],
|
|
429
|
+
toGetterBody({ literalArgs: [arrPath, sortDefPath] }) {
|
|
430
|
+
return `return arraySortedByProperties(${toAccess(arrPath)}, ${toAccess(sortDefPath)});`;
|
|
431
|
+
},
|
|
432
|
+
toDependentKeys({ literalArgs: [arrPath, sortDefPath] }) {
|
|
433
|
+
return [arrPath, sortDefPath];
|
|
434
|
+
},
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
// filter/map with callback — not auto-fixable
|
|
438
|
+
const filter = {
|
|
439
|
+
source: EMBER_SOURCE,
|
|
440
|
+
canAutoFix: false,
|
|
441
|
+
reason: "callback-based macro requires manual conversion",
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
const map = {
|
|
445
|
+
source: EMBER_SOURCE,
|
|
446
|
+
canAutoFix: false,
|
|
447
|
+
reason: "callback-based macro requires manual conversion",
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
// ---- discourse/lib/computed ------------------------------------------------
|
|
451
|
+
|
|
452
|
+
const propertyEqual = {
|
|
453
|
+
source: DISCOURSE_SOURCE,
|
|
454
|
+
canAutoFix: true,
|
|
455
|
+
requiredImports: [{ name: "deepEqual", source: "discourse/lib/object" }],
|
|
456
|
+
toGetterBody({ literalArgs: [a, b] }) {
|
|
457
|
+
return `return deepEqual(${toAccess(a)}, ${toAccess(b)});`;
|
|
458
|
+
},
|
|
459
|
+
toDependentKeys({ literalArgs: [a, b] }) {
|
|
460
|
+
return [a, b];
|
|
461
|
+
},
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
const propertyNotEqual = {
|
|
465
|
+
source: DISCOURSE_SOURCE,
|
|
466
|
+
canAutoFix: true,
|
|
467
|
+
requiredImports: [{ name: "deepEqual", source: "discourse/lib/object" }],
|
|
468
|
+
toGetterBody({ literalArgs: [a, b] }) {
|
|
469
|
+
return `return !deepEqual(${toAccess(a)}, ${toAccess(b)});`;
|
|
470
|
+
},
|
|
471
|
+
toDependentKeys({ literalArgs: [a, b] }) {
|
|
472
|
+
return [a, b];
|
|
473
|
+
},
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
const propertyGreaterThan = {
|
|
477
|
+
source: DISCOURSE_SOURCE,
|
|
478
|
+
canAutoFix: true,
|
|
479
|
+
toGetterBody({ literalArgs: [a, b] }) {
|
|
480
|
+
return `return ${toAccess(a)} > ${toAccess(b)};`;
|
|
481
|
+
},
|
|
482
|
+
toDependentKeys({ literalArgs: [a, b] }) {
|
|
483
|
+
return [a, b];
|
|
484
|
+
},
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
const propertyLessThan = {
|
|
488
|
+
source: DISCOURSE_SOURCE,
|
|
489
|
+
canAutoFix: true,
|
|
490
|
+
toGetterBody({ literalArgs: [a, b] }) {
|
|
491
|
+
return `return ${toAccess(a)} < ${toAccess(b)};`;
|
|
492
|
+
},
|
|
493
|
+
toDependentKeys({ literalArgs: [a, b] }) {
|
|
494
|
+
return [a, b];
|
|
495
|
+
},
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
const setting = {
|
|
499
|
+
source: DISCOURSE_SOURCE,
|
|
500
|
+
canAutoFix: true,
|
|
501
|
+
toGetterBody({ literalArgs: [name] }) {
|
|
502
|
+
return `return this.siteSettings.${name};`;
|
|
503
|
+
},
|
|
504
|
+
toDependentKeys({ literalArgs: [name] }) {
|
|
505
|
+
return [`siteSettings.${name}`];
|
|
506
|
+
},
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
const fmt = {
|
|
510
|
+
source: DISCOURSE_SOURCE,
|
|
511
|
+
canAutoFix: true,
|
|
512
|
+
toGetterBody({ literalArgs }) {
|
|
513
|
+
const format = literalArgs[literalArgs.length - 1];
|
|
514
|
+
const propPaths = literalArgs.slice(0, -1);
|
|
515
|
+
return `return ${fmtToTemplateLiteral(format, propPaths)};`;
|
|
516
|
+
},
|
|
517
|
+
toDependentKeys({ literalArgs }) {
|
|
518
|
+
return literalArgs.slice(0, -1);
|
|
519
|
+
},
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
const url = {
|
|
523
|
+
source: DISCOURSE_SOURCE,
|
|
524
|
+
canAutoFix: true,
|
|
525
|
+
requiredImports: [
|
|
526
|
+
{ name: "getURL", source: "discourse/lib/get-url", isDefault: true },
|
|
527
|
+
],
|
|
528
|
+
toGetterBody({ literalArgs }) {
|
|
529
|
+
const format = literalArgs[literalArgs.length - 1];
|
|
530
|
+
const propPaths = literalArgs.slice(0, -1);
|
|
531
|
+
return `return getURL(${fmtToTemplateLiteral(format, propPaths)});`;
|
|
532
|
+
},
|
|
533
|
+
toDependentKeys({ literalArgs }) {
|
|
534
|
+
return literalArgs.slice(0, -1);
|
|
535
|
+
},
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
const i18n = {
|
|
539
|
+
source: DISCOURSE_SOURCE,
|
|
540
|
+
canAutoFix: true,
|
|
541
|
+
requiredImports: [{ name: "i18n", source: "discourse-i18n" }],
|
|
542
|
+
toGetterBody({ literalArgs }) {
|
|
543
|
+
const format = literalArgs[literalArgs.length - 1];
|
|
544
|
+
const propPaths = literalArgs.slice(0, -1);
|
|
545
|
+
return `return i18n(${fmtToTemplateLiteral(format, propPaths)});`;
|
|
546
|
+
},
|
|
547
|
+
toDependentKeys({ literalArgs }) {
|
|
548
|
+
return literalArgs.slice(0, -1);
|
|
549
|
+
},
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
const htmlSafe = {
|
|
553
|
+
source: DISCOURSE_SOURCE,
|
|
554
|
+
canAutoFix: true,
|
|
555
|
+
requiredImports: [{ name: "htmlSafe", source: "@ember/template" }],
|
|
556
|
+
toGetterBody({ literalArgs: [path] }) {
|
|
557
|
+
return `return htmlSafe(${toAccess(path)});`;
|
|
558
|
+
},
|
|
559
|
+
toDependentKeys({ literalArgs: [path] }) {
|
|
560
|
+
return [path];
|
|
561
|
+
},
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
const endWith = {
|
|
565
|
+
source: DISCOURSE_SOURCE,
|
|
566
|
+
canAutoFix: true,
|
|
567
|
+
toGetterBody({ literalArgs }) {
|
|
568
|
+
const suffix = literalArgs[literalArgs.length - 1];
|
|
569
|
+
const propPaths = literalArgs.slice(0, -1);
|
|
570
|
+
if (propPaths.length === 1) {
|
|
571
|
+
return `return ${toAccess(propPaths[0])}?.endsWith(${renderLiteral(suffix)});`;
|
|
572
|
+
}
|
|
573
|
+
const checks = propPaths
|
|
574
|
+
.map((p) => `${toAccess(p)}?.endsWith(${renderLiteral(suffix)})`)
|
|
575
|
+
.join(" && ");
|
|
576
|
+
return `return ${checks};`;
|
|
577
|
+
},
|
|
578
|
+
toDependentKeys({ literalArgs }) {
|
|
579
|
+
return literalArgs.slice(0, -1);
|
|
580
|
+
},
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
// ---------------------------------------------------------------------------
|
|
584
|
+
// Registry
|
|
585
|
+
// ---------------------------------------------------------------------------
|
|
586
|
+
|
|
587
|
+
/** @type {Map<string, MacroTransform>} */
|
|
588
|
+
export const MACRO_TRANSFORMS = new Map([
|
|
589
|
+
// @ember/object/computed
|
|
590
|
+
["alias", alias],
|
|
591
|
+
["readOnly", readOnly],
|
|
592
|
+
["reads", reads],
|
|
593
|
+
["oneWay", oneWay],
|
|
594
|
+
["not", not],
|
|
595
|
+
["bool", bool],
|
|
596
|
+
["and", and],
|
|
597
|
+
["or", or],
|
|
598
|
+
["equal", equal],
|
|
599
|
+
["gt", gt],
|
|
600
|
+
["gte", gte],
|
|
601
|
+
["lt", lt],
|
|
602
|
+
["lte", lte],
|
|
603
|
+
["notEmpty", notEmpty],
|
|
604
|
+
["empty", empty],
|
|
605
|
+
["none", none],
|
|
606
|
+
["match", match],
|
|
607
|
+
["mapBy", mapBy],
|
|
608
|
+
["filterBy", filterBy],
|
|
609
|
+
["collect", collect],
|
|
610
|
+
["sum", sum],
|
|
611
|
+
["max", max],
|
|
612
|
+
["min", min],
|
|
613
|
+
["uniq", uniq],
|
|
614
|
+
["uniqBy", uniqBy],
|
|
615
|
+
["union", union],
|
|
616
|
+
["intersect", intersect],
|
|
617
|
+
["setDiff", setDiff],
|
|
618
|
+
["sort", sort],
|
|
619
|
+
["filter", filter],
|
|
620
|
+
["map", map],
|
|
621
|
+
// discourse/lib/computed
|
|
622
|
+
["propertyEqual", propertyEqual],
|
|
623
|
+
["propertyNotEqual", propertyNotEqual],
|
|
624
|
+
["propertyGreaterThan", propertyGreaterThan],
|
|
625
|
+
["propertyLessThan", propertyLessThan],
|
|
626
|
+
["setting", setting],
|
|
627
|
+
["fmt", fmt],
|
|
628
|
+
["url", url],
|
|
629
|
+
["i18n", i18n],
|
|
630
|
+
["computedI18n", i18n], // alias — exported as both names
|
|
631
|
+
["htmlSafe", htmlSafe],
|
|
632
|
+
["endWith", endWith],
|
|
633
|
+
]);
|
|
634
|
+
|
|
635
|
+
/** The import sources this rule targets. */
|
|
636
|
+
export const MACRO_SOURCES = new Set([
|
|
637
|
+
"@ember/object/computed",
|
|
638
|
+
"discourse/lib/computed",
|
|
639
|
+
"discourse/lib/decorators",
|
|
640
|
+
]);
|
|
641
|
+
|
|
642
|
+
/** Maps alternative import sources to the canonical source they alias. */
|
|
643
|
+
export const SOURCE_ALIASES = new Map([
|
|
644
|
+
["discourse/lib/decorators", "@ember/object/computed"],
|
|
645
|
+
]);
|