@wix/origami-lint 1.34.27 → 1.34.29
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/dist/rules/index.js +2 -1
- package/dist/rules/project/home-page-folder.js +3 -3
- package/dist/rules/site-app/apply-and-presets.js +13 -13
- package/dist/rules/site-app/bindings.js +135 -68
- package/dist/rules/site-app/breakpoints.js +6 -6
- package/dist/rules/site-app/css-file-nesting.js +13 -13
- package/dist/rules/site-app/css-layout-properties.js +14 -15
- package/dist/rules/site-app/css-selectors.js +18 -18
- package/dist/rules/site-app/global-styles.js +19 -19
- package/dist/rules/site-app/no-free-code.js +3 -3
- package/dist/rules/site-app/page-structure.js +11 -11
- package/dist/rules/site-app/slot-attributes.js +4 -4
- package/dist/rules/site-app/slot-placement.js +3 -3
- package/dist/rules/site-app/theme-font-ids.js +7 -49
- package/dist/rules/site-app/theme-font-known.js +30 -0
- package/dist/rules/site-app/theme-root.js +7 -8
- package/dist/rules-api/index.js +2 -2
- package/dist/rules-api/rules-api.js +2 -1
- package/dist/shared/constants.js +4 -0
- package/dist/shared/css.js +54 -0
- package/package.json +2 -2
|
@@ -119,28 +119,29 @@ function checkSvgArguments({ node, rulesApi, report }) {
|
|
|
119
119
|
return;
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
|
+
// tracker CONV-vectorArtExpression.ts:44
|
|
122
123
|
report({ check: 'invalid_svg_expression',
|
|
123
124
|
loc: node.loc,
|
|
124
|
-
message:
|
|
125
|
+
message: "An SVG media expression must be `svg(media.getShapeUrl('wix:image://v1/<uri>').url, '<uri>')`, with matching URI strings, svg imported from '@wix/origami-runtime' and media from '@wix/sdk'.",
|
|
125
126
|
});
|
|
126
127
|
}
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
const tracked = (message, row) => `${message.slice(0, -1)} (tracker ${row}).`;
|
|
130
|
-
const say = ({ input }, node, check, message, row) => input.report({ check, loc: node.loc, message: tracked(message, row) });
|
|
128
|
+
// ── the steps ──
|
|
129
|
+
const say = ({ input }, node, check, message) => input.report({ check, loc: node.loc, message });
|
|
131
130
|
const textOf = ({ input }, node) => input.context.sourceCode.getText(node);
|
|
132
131
|
const describeIn = (at, node) => describe(node, (part) => textOf(at, part));
|
|
133
132
|
function removeWhen(at) {
|
|
134
133
|
const { node } = at.input;
|
|
135
134
|
const when = getAttribute(node, 'when');
|
|
136
135
|
if (!when) {
|
|
137
|
-
|
|
136
|
+
// tracker CONV-parseJsx.ts:1113
|
|
137
|
+
say(at, node.openingElement.name, 'missing_remove_condition', `${at.owner} needs \`when={…}\`: the condition that removes the component it wraps.`);
|
|
138
138
|
return null;
|
|
139
139
|
}
|
|
140
140
|
const value = getExpression(when);
|
|
141
141
|
const what = when.value ? `\`${at.input.context.sourceCode.getText(when.value)}\`` : 'empty';
|
|
142
|
+
// tracker CONV-parseJsx.ts:1113
|
|
142
143
|
if (!value)
|
|
143
|
-
say(at, when, 'invalid_remove_condition', `\`when\` on ${at.owner} is ${what}. Write the condition in braces: \`when={…}
|
|
144
|
+
say(at, when, 'invalid_remove_condition', `\`when\` on ${at.owner} is ${what}. Write the condition in braces: \`when={…}\`.`);
|
|
144
145
|
return value;
|
|
145
146
|
}
|
|
146
147
|
function removeTarget(at) {
|
|
@@ -153,30 +154,48 @@ function removeTarget(at) {
|
|
|
153
154
|
const item = getObject(value.arguments[0]);
|
|
154
155
|
// An item of another type is the bindings' own line (checkRawBindingsType).
|
|
155
156
|
if (item && stringValue(getProperty(item, 'type')?.value) === BINDINGS_LIST && setsRemoved(item)) {
|
|
156
|
-
|
|
157
|
+
// tracker CONV-decode.ts:280
|
|
158
|
+
say(at, value, 'duplicate_remove_condition', `${at.owner} wraps ${ownerOf(component)}, whose \`bindings\` are \`raw()\` of an item that already sets \`features.visibility.${REMOVED}\`. Keep only one of them.`);
|
|
157
159
|
}
|
|
158
160
|
}
|
|
159
161
|
}
|
|
160
162
|
function builderInPlace(at) {
|
|
161
163
|
const { node, rulesApi } = at.input;
|
|
162
164
|
for (const [call, where] of misplacedBuilders(node, rulesApi)) {
|
|
163
|
-
|
|
165
|
+
// tracker CONV-astValue.ts:98
|
|
166
|
+
say(at, call, 'nested_binding_builder', `\`${builderOf(call, rulesApi)}()\` on ${at.owner} is inside ${where}, where the import reads only plain values. Write a builder as the whole value of a data attribute (\`label={ctx(…)}\`) or of an elementProps entry key (\`navbar: { label: ctx(…) }\`), or in \`bindings\`.`);
|
|
164
167
|
}
|
|
165
168
|
}
|
|
166
|
-
/**
|
|
169
|
+
/**
|
|
170
|
+
* A callback prop takes a function: on a component that takes data, the import reads a `callback()` attribute as a binding (parseJsx.ts
|
|
171
|
+
* isCallbackAttr), and refuses an `on…` attribute with any other call. In `bindings.props` the site's runtime throws on anything but `callback()`
|
|
172
|
+
* (withOrigami).
|
|
173
|
+
*/
|
|
167
174
|
function checkCallbackProps(at) {
|
|
168
175
|
const { node, rulesApi } = at.input;
|
|
169
|
-
|
|
170
|
-
|
|
176
|
+
const takesData = rulesApi.hasDataAttributes(node);
|
|
177
|
+
for (const [name, attribute, value] of getCallbackAttributes(node)) {
|
|
178
|
+
const isCallback = builderOf(value, rulesApi) === 'callback';
|
|
179
|
+
// tracker OUR-031
|
|
180
|
+
if (!takesData)
|
|
181
|
+
say(at, attribute, 'callback_attribute', `\`${name}\` on ${at.owner} is an attribute, which the import doesn't read on this component. ${CALLBACK_FIX(`bindings={{ props: { ${name}: callback(…) } }}`)}`);
|
|
182
|
+
else if (!isCallback)
|
|
183
|
+
say(at, attribute, 'callback_attribute', `\`${name}\` on ${at.owner} is ${describeCall(at, value)}, which the import doesn't read for a callback prop. ${CALLBACK_FIX(`${name}={callback(…)}`)}`);
|
|
171
184
|
}
|
|
172
185
|
if (!isWrappedByRuntime(node, rulesApi))
|
|
173
186
|
return;
|
|
174
187
|
for (const [name, value] of getBoundCallbacks(getAttribute(node, 'bindings'))) {
|
|
175
188
|
const what = runsAtRender(at, value);
|
|
189
|
+
// tracker OUR-031
|
|
176
190
|
if (what)
|
|
177
|
-
say(at, value, 'non_function_callback', `\`bindings.props.${name}\` on ${at.owner} is ${what}, which
|
|
191
|
+
say(at, value, 'non_function_callback', `\`bindings.props.${name}\` on ${at.owner} is ${what}, which gives a value, not a function: the site's runtime throws on it. ${CALLBACK_FIX(`${name}: callback(…)`)}`);
|
|
178
192
|
}
|
|
179
193
|
}
|
|
194
|
+
const CALLBACK_FIX = (form) => `Write \`${form}\`, its first argument an imported function, a \`ctx()\` or a member of the renderItem row.`;
|
|
195
|
+
const describeCall = (at, node) => {
|
|
196
|
+
const builder = builderOf(node, at.input.rulesApi);
|
|
197
|
+
return builder ? `\`${builder}()\`` : describeIn(at, node);
|
|
198
|
+
};
|
|
180
199
|
function boundPropOnce(at) {
|
|
181
200
|
const { node, rulesApi } = at.input;
|
|
182
201
|
const bindings = getAttribute(node, 'bindings');
|
|
@@ -185,8 +204,9 @@ function boundPropOnce(at) {
|
|
|
185
204
|
return;
|
|
186
205
|
for (const [name, attribute] of boundAttributes(node, rulesApi)) {
|
|
187
206
|
const where = props.raw ? `bindings={raw({ properties: { ${name} } })}` : `bindings={{ props: { ${name} } }}`;
|
|
207
|
+
// tracker CONV-parseJsx.ts:753
|
|
188
208
|
if (props.names.has(name))
|
|
189
|
-
say(at, attribute, 'duplicate_binding', `${at.owner} binds \`${name}\` twice: in \`${name}={…}\` and in \`${where}\`. Keep one
|
|
209
|
+
say(at, attribute, 'duplicate_binding', `${at.owner} binds \`${name}\` twice: in \`${name}={…}\` and in \`${where}\`. Keep one.`);
|
|
190
210
|
}
|
|
191
211
|
}
|
|
192
212
|
function entryPropBoundOnce(at, entry) {
|
|
@@ -195,8 +215,9 @@ function entryPropBoundOnce(at, entry) {
|
|
|
195
215
|
return;
|
|
196
216
|
for (const [key, property] of boundEntryKeys(entry, at.input.rulesApi)) {
|
|
197
217
|
const where = props.raw ? `bindings: raw({ properties: { ${key} } })` : `bindings.props.${key}`;
|
|
218
|
+
// tracker CONV-parseJsx.ts:753
|
|
198
219
|
if (props.names.has(key))
|
|
199
|
-
say(at, property, 'duplicate_binding', `${at.owner} binds \`${key}\` twice: in \`${key}: …\` and in its \`${where}\`. Keep one
|
|
220
|
+
say(at, property, 'duplicate_binding', `${at.owner} binds \`${key}\` twice: in \`${key}: …\` and in its \`${where}\`. Keep one.`);
|
|
200
221
|
}
|
|
201
222
|
}
|
|
202
223
|
function bindingsAttribute(at, attribute) {
|
|
@@ -204,9 +225,9 @@ function bindingsAttribute(at, attribute) {
|
|
|
204
225
|
return;
|
|
205
226
|
const value = getExpression(attribute);
|
|
206
227
|
if (value)
|
|
207
|
-
bindingsValue(at, value);
|
|
208
|
-
|
|
209
|
-
|
|
228
|
+
return bindingsValue(at, value);
|
|
229
|
+
// tracker CONV-parseJsx.ts:700
|
|
230
|
+
say(at, attribute, 'invalid_bindings_object', notBindings(at, attribute.value ? `\`${at.input.context.sourceCode.getText(attribute.value)}\`` : 'empty'));
|
|
210
231
|
}
|
|
211
232
|
const notBindings = (at, what) => `\`bindings\` on ${at.owner} is ${what}. Write an object in place, \`bindings={{ props: {…} }}\`, or \`raw({…})\`.`;
|
|
212
233
|
function bindingsValue(at, node, isEntry = false) {
|
|
@@ -221,22 +242,27 @@ function bindingsValue(at, node, isEntry = false) {
|
|
|
221
242
|
const object = bare(item);
|
|
222
243
|
if (!isEntry)
|
|
223
244
|
checkRawBindingsType(at, value, object);
|
|
245
|
+
// tracker CONV-decode.ts:280
|
|
224
246
|
if (!isEntry && setsRemoved(object))
|
|
225
|
-
say(at, value, 'removed_in_raw_bindings', `${at.owner} sets \`features.visibility.${REMOVED}\` in its \`raw()\` bindings. Move the condition out, and wrap the component in \`<Remove when={…}
|
|
247
|
+
say(at, value, 'removed_in_raw_bindings', `${at.owner} sets \`features.visibility.${REMOVED}\` in its \`raw()\` bindings. Move the condition out, and wrap the component in \`<Remove when={…}>\`.`);
|
|
226
248
|
const propertyMap = getProperty(object, 'properties');
|
|
227
249
|
const properties = getObject(propertyMap?.value);
|
|
250
|
+
// tracker CONV-decode.ts:252
|
|
228
251
|
if (propertyMap && !properties)
|
|
229
|
-
say(at, propertyMap.value, 'invalid_raw_properties', `The \`properties\` in \`raw()\` bindings on ${at.owner} isn't an object. Write a prop map of typed bindings: \`properties: { label: { type: 'ContextBinding', … } }
|
|
252
|
+
say(at, propertyMap.value, 'invalid_raw_properties', `The \`properties\` in \`raw()\` bindings on ${at.owner} isn't an object. Write a prop map of typed bindings: \`properties: { label: { type: 'ContextBinding', … } }\`.`);
|
|
230
253
|
for (const property of properties ? getProperties(properties) : []) {
|
|
254
|
+
// tracker CONV-decode.ts:252
|
|
231
255
|
if (isStaticItem(property.value))
|
|
232
|
-
say(at, property.value, 'static_binding_value', fixedValue(at, getKey(property))
|
|
256
|
+
say(at, property.value, 'static_binding_value', fixedValue(at, getKey(property)));
|
|
233
257
|
}
|
|
234
258
|
}
|
|
235
259
|
else if (builder) {
|
|
236
|
-
|
|
260
|
+
// tracker CONV-decode.ts:263
|
|
261
|
+
say(at, value, 'builder_as_bindings', `\`bindings\` on ${at.owner} is a \`${builder}()\` call. Write an object and put the call under props, or use \`raw({…})\`.`);
|
|
237
262
|
}
|
|
238
263
|
else if (!isCallByName(value)) {
|
|
239
|
-
|
|
264
|
+
// tracker CONV-parseJsx.ts:700; an elementProps entry's: CONV-innerElements.ts:221
|
|
265
|
+
say(at, value, 'invalid_bindings_object', notBindings(at, describeIn(at, value)));
|
|
240
266
|
}
|
|
241
267
|
}
|
|
242
268
|
function bindingsObject(at, bindings, isEntry) {
|
|
@@ -247,8 +273,9 @@ function bindingsObject(at, bindings, isEntry) {
|
|
|
247
273
|
: property.key.type !== 'Identifier' ? `the key \`${textOf(at, property.key)}\``
|
|
248
274
|
: property.key.name === BINDINGS_KEY || CLASS_NAME_SECTIONS.includes(property.key.name) ? null
|
|
249
275
|
: `the key \`${property.key.name}\``;
|
|
276
|
+
// tracker CONV-parseJsx.ts:708
|
|
250
277
|
if (what)
|
|
251
|
-
say(at, property, 'invalid_bindings_key', `\`bindings\` on ${at.owner} has ${what}. Use only the key props, written as a plain name
|
|
278
|
+
say(at, property, 'invalid_bindings_key', `\`bindings\` on ${at.owner} has ${what}. Use only the key props, written as a plain name.`);
|
|
252
279
|
else if (getKey(property) === BINDINGS_KEY)
|
|
253
280
|
section(at, BINDINGS_KEY, property.value);
|
|
254
281
|
else
|
|
@@ -259,17 +286,20 @@ function bindingsObject(at, bindings, isEntry) {
|
|
|
259
286
|
function classNameSection(at, property, isEntry) {
|
|
260
287
|
const key = getKey(property);
|
|
261
288
|
const removed = key === 'visibility' && !isEntry && getObject(property.value) && getProperty(getObject(property.value), REMOVED);
|
|
289
|
+
// tracker CONV-decode.ts:280
|
|
262
290
|
if (removed)
|
|
263
|
-
return say(at, removed, 'removed_in_bindings', `${at.owner} sets \`visibility.${REMOVED}\` in its \`bindings\`. Move the condition out, and wrap the component in \`<Remove when={…}
|
|
291
|
+
return say(at, removed, 'removed_in_bindings', `${at.owner} sets \`visibility.${REMOVED}\` in its \`bindings\`. Move the condition out, and wrap the component in \`<Remove when={…}>\`.`);
|
|
264
292
|
const term = key === 'states' ? '{ hover: is(…) }' : "{ 'wix-visibility-hidden': is(…) }";
|
|
265
293
|
const fix = isEntry ? `in the entry's \`className: ${term}\`` : `as a \`className\` term: \`className={['comp-x', ${term}]}\``;
|
|
266
|
-
|
|
294
|
+
// tracker CONV-parseJsx.ts:708
|
|
295
|
+
say(at, property, 'state_in_bindings', `\`bindings.${key}\` on ${at.owner} isn't read: the import reads states and visibility in \`className\`. Write each condition ${fix}.`);
|
|
267
296
|
}
|
|
268
297
|
function section(at, name, node) {
|
|
269
298
|
const fix = `Write an object of \`key: value\` entries: \`${name}: { label: … }\`.`;
|
|
270
299
|
const value = bare(node);
|
|
300
|
+
// tracker CONV-parseJsx.ts:669
|
|
271
301
|
if (value.type !== 'ObjectExpression')
|
|
272
|
-
return say(at, value, 'invalid_binding_map', `\`bindings.${name}\` on ${at.owner} isn't an object. ${fix}
|
|
302
|
+
return say(at, value, 'invalid_binding_map', `\`bindings.${name}\` on ${at.owner} isn't an object. ${fix}`);
|
|
273
303
|
for (const property of value.properties) {
|
|
274
304
|
const problem = property.type === 'SpreadElement' ? 'has a spread'
|
|
275
305
|
: property.computed ? 'has a computed key'
|
|
@@ -279,7 +309,8 @@ function section(at, name, node) {
|
|
|
279
309
|
: getKey(property) === null ? `has the key \`${textOf(at, property.key)}\``
|
|
280
310
|
: null;
|
|
281
311
|
if (problem) {
|
|
282
|
-
|
|
312
|
+
// tracker CONV-parseJsx.ts:669
|
|
313
|
+
say(at, property, 'invalid_binding_map_entry', `\`bindings.${name}\` on ${at.owner} ${problem}. ${fix}`);
|
|
283
314
|
continue;
|
|
284
315
|
}
|
|
285
316
|
prop(at, getKey(property), property.value, false);
|
|
@@ -301,7 +332,8 @@ function classNameConditions(at, terms, where) {
|
|
|
301
332
|
if (key === null)
|
|
302
333
|
continue;
|
|
303
334
|
if (key.startsWith(VISIBILITY_PREFIX) && !VISIBILITY_KEYS.includes(key.slice(VISIBILITY_PREFIX.length))) {
|
|
304
|
-
|
|
335
|
+
// tracker CONV-decode.ts:221
|
|
336
|
+
say(at, property, 'unknown_visibility', `${where} on ${at.owner} has the term \`${key}\`, which isn't a visibility. Use \`${VISIBILITY_PREFIX}hidden\` or \`${VISIBILITY_PREFIX}collapsed\`.`);
|
|
305
337
|
}
|
|
306
338
|
else
|
|
307
339
|
condition(at, property.value, `${where} term \`${key}\``);
|
|
@@ -309,10 +341,12 @@ function classNameConditions(at, terms, where) {
|
|
|
309
341
|
}
|
|
310
342
|
function prop(at, name, node, placeholderAllowed) {
|
|
311
343
|
const value = bare(node);
|
|
344
|
+
// tracker CONV-decode.ts:209
|
|
312
345
|
if (value.type === 'ArrayExpression')
|
|
313
|
-
return say(at, value, 'array_binding_value', `\`bindings.props.${name}\` on ${at.owner} is an array. Bind the prop to one value: \`ctx()\`, \`fn()\` or \`raw()
|
|
346
|
+
return say(at, value, 'array_binding_value', `\`bindings.props.${name}\` on ${at.owner} is an array. Bind the prop to one value: \`ctx()\`, \`fn()\` or \`raw()\`.`);
|
|
347
|
+
// tracker CONV-decode.ts:252
|
|
314
348
|
if (isPlain(value) || rawOnly(value) || isStaticRaw(at, value) || isTyped(value, at.input.rulesApi))
|
|
315
|
-
return say(at, value, 'static_binding_value', fixedValue(at, name)
|
|
349
|
+
return say(at, value, 'static_binding_value', fixedValue(at, name));
|
|
316
350
|
binding(at, value, 'prop', placeholderAllowed);
|
|
317
351
|
}
|
|
318
352
|
const fixedValue = (at, name) => `\`${name}\` on ${at.owner} is bound to a fixed value. Write it as the attribute \`${name}=…\`, or as an \`fn()\` argument.`;
|
|
@@ -328,7 +362,8 @@ function condition(at, node, where, inRemove = false) {
|
|
|
328
362
|
return binding(at, value, 'condition');
|
|
329
363
|
if (inRemove || value.elements.length > 0) {
|
|
330
364
|
const spread = hasHoleOrSpread(value);
|
|
331
|
-
|
|
365
|
+
// tracker CONV-decode.ts:190; a list with a spread or a hole: CONV-parseJsx.ts:655
|
|
366
|
+
say(at, value, 'array_condition', `${where} on ${at.owner} is ${spread ? 'a list with a spread or an empty place' : 'a list'}. Write one condition, or \`all(a, b, …)\`.`);
|
|
332
367
|
}
|
|
333
368
|
}
|
|
334
369
|
function binding(at, node, place, placeholderAllowed = false) {
|
|
@@ -357,30 +392,35 @@ function binding(at, node, place, placeholderAllowed = false) {
|
|
|
357
392
|
const raw = rawOnly(value);
|
|
358
393
|
if (raw === 'an object' && place === 'fnArg' && isDataItem(value))
|
|
359
394
|
return;
|
|
395
|
+
// tracker CONV-decode.ts:86
|
|
360
396
|
if (raw === 'an object' && place === 'fnArg')
|
|
361
|
-
return say(at, value, 'invalid_binding_value', `${capitalize(name)} on ${at.owner} is an object that isn't a data item. Write ${PLAIN}, a link (\`{ href: … }\`) or a typed item (\`{ type: 'Builder.Image', … }\`), or wrap it in \`raw()
|
|
397
|
+
return say(at, value, 'invalid_binding_value', `${capitalize(name)} on ${at.owner} is an object that isn't a data item. Write ${PLAIN}, a link (\`{ href: … }\`) or a typed item (\`{ type: 'Builder.Image', … }\`), or wrap it in \`raw()\`.`);
|
|
362
398
|
if (raw)
|
|
363
|
-
return say(at, value, 'invalid_binding_value', `${capitalize(name)} on ${at.owner} is ${raw}. Write ${PLAIN}, or wrap it in \`raw()
|
|
364
|
-
say(at, value, 'invalid_binding_value', `${capitalize(name)} on ${at.owner} is ${describeIn(at, value)}. Write it out. ${hintFor(place)}
|
|
399
|
+
return say(at, value, 'invalid_binding_value', `${capitalize(name)} on ${at.owner} is ${raw}. Write ${PLAIN}, or wrap it in \`raw()\`.`);
|
|
400
|
+
say(at, value, 'invalid_binding_value', `${capitalize(name)} on ${at.owner} is ${describeIn(at, value)}. Write it out. ${hintFor(place)}`);
|
|
365
401
|
}
|
|
366
|
-
|
|
402
|
+
// tracker CONV-decode.ts:126
|
|
403
|
+
const wrongPlace = (at, node, what, place) => say(at, node, 'invalid_binding_placement', `${capitalize(what)} can't be ${PLACES[place].name} on ${at.owner}. ${hintFor(place)}`);
|
|
367
404
|
/** A called ctx() or row member: `ctx('cart.add', { from: useCart })(item.id)`, `item.actions.select('quick')` (decode.ts decodeInvoke). */
|
|
368
405
|
function invoke(at, node) {
|
|
369
406
|
const { callee } = node;
|
|
407
|
+
// tracker CONV-decode.ts:86
|
|
370
408
|
if (!isMethodTarget(callee, at.input.rulesApi))
|
|
371
|
-
return say(at, node, 'invalid_method_target', `${capitalize(describeIn(at, node))} on ${at.owner} calls what isn't a context or the renderItem row. Call only \`ctx(…)\` or a member of the row: \`ctx('cart.add', { from: useCart })(item.id)
|
|
409
|
+
return say(at, node, 'invalid_method_target', `${capitalize(describeIn(at, node))} on ${at.owner} calls what isn't a context or the renderItem row. Call only \`ctx(…)\` or a member of the row: \`ctx('cart.add', { from: useCart })(item.id)\`.`);
|
|
372
410
|
if (callee.type === 'CallExpression')
|
|
373
411
|
ctxCall(at, callee, false);
|
|
412
|
+
// tracker CONV-astValue.ts:310
|
|
374
413
|
if (node.arguments.some((argument) => argument.type === 'SpreadElement'))
|
|
375
|
-
return say(at, node, 'spread_builder_arguments', `A called \`ctx()\` or row member on ${at.owner} spreads its arguments. Write each argument out
|
|
414
|
+
return say(at, node, 'spread_builder_arguments', `A called \`ctx()\` or row member on ${at.owner} spreads its arguments. Write each argument out.`);
|
|
376
415
|
for (const argument of node.arguments)
|
|
377
416
|
binding(at, argument, 'fnArg');
|
|
378
417
|
}
|
|
379
418
|
function call(at, node, builder, place, placeholderAllowed) {
|
|
380
419
|
if (node.arguments.some((argument) => argument.type === 'SpreadElement')) {
|
|
381
|
-
|
|
420
|
+
// tracker CONV-astValue.ts:310
|
|
421
|
+
return say(at, node, 'spread_builder_arguments', `\`${builder}()\` on ${at.owner} spreads its arguments. Write each argument out.`);
|
|
382
422
|
}
|
|
383
|
-
// map() and shape() hold a callback, read in its own scope:
|
|
423
|
+
// map() and shape() hold a callback, read in its own scope: nothing checks its shape yet.
|
|
384
424
|
if (builder === 'map' || builder === 'shape' || hasDuplicateKey(at, node, builder))
|
|
385
425
|
return;
|
|
386
426
|
if (builder === 'ctx')
|
|
@@ -398,8 +438,9 @@ function call(at, node, builder, place, placeholderAllowed) {
|
|
|
398
438
|
}
|
|
399
439
|
function hasDuplicateKey(at, node, builder) {
|
|
400
440
|
const twice = node.arguments.map(duplicateKeyIn).find(Boolean);
|
|
441
|
+
// tracker, by builder: all CONV-decode.ts:196, callback and fn CONV-astValue.ts:287, ctx CONV-astValue.ts:276, is CONV-decode.ts:172, raw CONV-decode.ts:28
|
|
401
442
|
if (twice)
|
|
402
|
-
say(at, twice, 'duplicate_builder_key', `\`${builder}()\` on ${at.owner} has the key \`${getKey(twice)}\` twice. Keep one
|
|
443
|
+
say(at, twice, 'duplicate_builder_key', `\`${builder}()\` on ${at.owner} has the key \`${getKey(twice)}\` twice. Keep one.`);
|
|
403
444
|
return Boolean(twice);
|
|
404
445
|
}
|
|
405
446
|
function ctxCall(at, node, placeholderAllowed) {
|
|
@@ -415,16 +456,18 @@ function ctxCall(at, node, placeholderAllowed) {
|
|
|
415
456
|
: !keys.includes('from') ? 'has no `from`'
|
|
416
457
|
: keys.includes('placeholder') && !placeholderAllowed ? 'has a placeholder: only a whole attribute value, or an elementProps entry value, has one'
|
|
417
458
|
: null;
|
|
459
|
+
// tracker CONV-astValue.ts:276
|
|
418
460
|
if (problem)
|
|
419
|
-
return say(at, node, 'invalid_context_arguments', `\`ctx()\` on ${at.owner} ${problem}. Write \`ctx('item.path', { from: useX })
|
|
461
|
+
return say(at, node, 'invalid_context_arguments', `\`ctx()\` on ${at.owner} ${problem}. Write \`ctx('item.path', { from: useX })\`.`);
|
|
420
462
|
const placeholder = object && getProperty(object, 'placeholder');
|
|
421
463
|
if (placeholder)
|
|
422
464
|
placeholderValue(at, placeholder.value);
|
|
423
465
|
}
|
|
424
466
|
function fnCall(at, node, placeholderAllowed) {
|
|
425
467
|
const [, ...rest] = node.arguments;
|
|
468
|
+
// tracker CONV-astValue.ts:287
|
|
426
469
|
if (node.arguments.length === 0)
|
|
427
|
-
return say(at, node, 'missing_function_argument', `\`fn()\` on ${at.owner} has no arguments. Write the imported function first: \`fn(numberToText, …)
|
|
470
|
+
return say(at, node, 'missing_function_argument', `\`fn()\` on ${at.owner} has no arguments. Write the imported function first: \`fn(numberToText, …)\`.`);
|
|
428
471
|
const last = rest.at(-1);
|
|
429
472
|
const options = placeholderAllowed && last ? getObject(last) : null;
|
|
430
473
|
const placeholder = options && options.properties.length === 1 ? getProperty(options, 'placeholder') : undefined;
|
|
@@ -438,35 +481,44 @@ function fnCall(at, node, placeholderAllowed) {
|
|
|
438
481
|
/** `callback(goHome, 2)`: the function (an imported one, a `ctx()` or a member of the row), then its bound arguments (decode.ts decodeCallback). */
|
|
439
482
|
function callbackCall(at, node) {
|
|
440
483
|
const [first, ...bound] = node.arguments;
|
|
484
|
+
// tracker CONV-astValue.ts:287
|
|
441
485
|
if (!first)
|
|
442
|
-
return say(at, node, 'missing_function_argument', `\`callback()\` on ${at.owner} has no arguments. Write the function first: \`callback(
|
|
486
|
+
return say(at, node, 'missing_function_argument', `\`callback()\` on ${at.owner} has no arguments. Write the function first: \`callback(<function>, …)\`.`);
|
|
443
487
|
const target = bare(first);
|
|
444
488
|
// A name that isn't a parameter is the imported function: bindingNameImported checks its import.
|
|
445
489
|
const isFunctionName = target.type === 'Identifier' && !getParamReadKind(target);
|
|
490
|
+
// tracker CONV-decode.ts:86
|
|
446
491
|
if (!isFunctionName && !isMethodTarget(target, at.input.rulesApi))
|
|
447
|
-
return say(at, target, 'invalid_callback_target', `\`callback()\`'s function on ${at.owner} is ${describeIn(at, target)}. Write an imported function, a \`ctx()\` or a member of the renderItem row: \`callback(
|
|
492
|
+
return say(at, target, 'invalid_callback_target', `\`callback()\`'s function on ${at.owner} is ${describeIn(at, target)}. Write an imported function, a \`ctx()\` or a member of the renderItem row: \`callback(<function>, …)\`.`);
|
|
448
493
|
if (target.type === 'CallExpression')
|
|
449
494
|
ctxCall(at, target, false);
|
|
450
495
|
for (const argument of bound)
|
|
451
496
|
binding(at, argument, 'fnArg');
|
|
452
497
|
}
|
|
498
|
+
/** The import stores any JSON value but null as the prop's data (boundProps.ts isPlaceholderValue): the entries of a list, a rich text item. */
|
|
453
499
|
function placeholderValue(at, node) {
|
|
454
500
|
const value = bare(node);
|
|
455
|
-
|
|
456
|
-
|
|
501
|
+
const isNull = rawOnly(value) === 'null';
|
|
502
|
+
const found = isNull ? value : nonLiteralIn(value);
|
|
503
|
+
// tracker CONV-parseJsx.ts:729
|
|
504
|
+
if (found)
|
|
505
|
+
say(at, found, 'invalid_placeholder', `The placeholder on ${at.owner} ${found === value ? 'is' : 'holds'} ${isNull ? 'null' : describeIn(at, found)}. Write ${PLAIN}, or an object or array of them.`);
|
|
457
506
|
}
|
|
458
507
|
function isCall(at, node) {
|
|
459
508
|
const fix = 'Write `is(<ctx() or fn()>, <string or true / false>)`.';
|
|
509
|
+
// tracker CONV-decode.ts:172
|
|
460
510
|
if (node.arguments.length !== 2)
|
|
461
|
-
return say(at, node, 'invalid_comparison_arguments', `\`is()\` on ${at.owner} has ${node.arguments.length} argument${node.arguments.length === 1 ? '' : 's'}. ${fix}
|
|
511
|
+
return say(at, node, 'invalid_comparison_arguments', `\`is()\` on ${at.owner} has ${node.arguments.length} argument${node.arguments.length === 1 ? '' : 's'}. ${fix}`);
|
|
462
512
|
const [binded, compared] = node.arguments.map((argument) => bare(argument));
|
|
513
|
+
// tracker CONV-decode.ts:172
|
|
463
514
|
if (isPlain(binded) || rawOnly(binded) || builderOf(binded, at.input.rulesApi) === 'raw' || isTyped(binded, at.input.rulesApi))
|
|
464
|
-
say(at, binded, 'literal_comparison_binding', `\`is()\` on ${at.owner} compares ${isCallByName(binded) ? `\`${builderOf(binded, at.input.rulesApi)}()\`` : 'a literal'}, not a binding. ${fix}
|
|
515
|
+
say(at, binded, 'literal_comparison_binding', `\`is()\` on ${at.owner} compares ${isCallByName(binded) ? `\`${builderOf(binded, at.input.rulesApi)}()\`` : 'a literal'}, not a binding. ${fix}`);
|
|
465
516
|
else
|
|
466
517
|
binding(at, binded, 'isBinding');
|
|
467
518
|
const isStringOrBoolean = compared.type === 'Literal' && ['string', 'boolean'].includes(typeof compared.value);
|
|
519
|
+
// tracker CONV-decode.ts:172
|
|
468
520
|
if (!isStringOrBoolean)
|
|
469
|
-
say(at, compared, 'invalid_comparison_value', `\`is()\` on ${at.owner} compares to ${rawOnly(compared) ?? describeIn(at, compared)}. ${fix}
|
|
521
|
+
say(at, compared, 'invalid_comparison_value', `\`is()\` on ${at.owner} compares to ${rawOnly(compared) ?? describeIn(at, compared)}. ${fix}`);
|
|
470
522
|
}
|
|
471
523
|
/**
|
|
472
524
|
* A component's `raw()` bindings are one item of the bindings namespace, and its one schema is BindingsList (reservedAttributes.ts
|
|
@@ -478,33 +530,39 @@ function checkRawBindingsType(at, node, item) {
|
|
|
478
530
|
if (stringValue(type?.value) === BINDINGS_LIST)
|
|
479
531
|
return;
|
|
480
532
|
const has = type ? `has the type \`${textOf(at, type.value)}\`` : 'has no `type`';
|
|
481
|
-
|
|
533
|
+
// tracker CONV-decode.ts:28; under a <Remove>, also CONV-decode.ts:296
|
|
534
|
+
say(at, type?.value ?? node, 'invalid_raw_bindings_type', `The \`raw()\` bindings item on ${at.owner} ${has}. Write \`raw({ type: '${BINDINGS_LIST}', … })\`: the import stores no other bindings item.`);
|
|
482
535
|
}
|
|
483
536
|
function allCall(at, node, place) {
|
|
484
537
|
const count = node.arguments.length;
|
|
538
|
+
// tracker CONV-decode.ts:196
|
|
485
539
|
if (place === 'condition' && count < 2)
|
|
486
|
-
say(at, node, 'insufficient_conditions', `\`all()\` on ${at.owner} has ${count} condition${count === 1 ? '' : 's'}. Give it two or more, or write the one condition without \`all()
|
|
540
|
+
say(at, node, 'insufficient_conditions', `\`all()\` on ${at.owner} has ${count} condition${count === 1 ? '' : 's'}. Give it two or more, or write the one condition without \`all()\`.`);
|
|
487
541
|
for (const argument of node.arguments)
|
|
488
542
|
binding(at, argument, 'allItem');
|
|
489
543
|
}
|
|
490
544
|
function isRawWrittenOut(at, node) {
|
|
491
545
|
const [item, ...extra] = node.arguments;
|
|
492
546
|
if (!item || extra.length > 0) {
|
|
493
|
-
|
|
547
|
+
// tracker CONV-decode.ts:28
|
|
548
|
+
say(at, node, 'invalid_raw_arguments', `\`raw()\` on ${at.owner} has ${node.arguments.length} arguments. Write one item: \`raw({ type: …, … })\`.`);
|
|
494
549
|
return false;
|
|
495
550
|
}
|
|
496
551
|
const value = bare(item);
|
|
497
552
|
if (value.type === 'CallExpression') {
|
|
498
|
-
|
|
553
|
+
// tracker CONV-decode.ts:28
|
|
554
|
+
say(at, value, 'call_in_raw', `\`raw()\` on ${at.owner} wraps a call (\`${textOf(at, value)}\`). Write the item out as an object, or use the builder without \`raw()\`.`);
|
|
499
555
|
return false;
|
|
500
556
|
}
|
|
501
557
|
if (value.type !== 'ObjectExpression') {
|
|
502
|
-
|
|
558
|
+
// tracker CONV-decode.ts:28
|
|
559
|
+
say(at, value, 'non_object_raw', `\`raw()\` on ${at.owner} wraps ${rawOnly(value) ?? describeIn(at, value)}, not an object. Write one item: \`raw({ type: …, … })\`.`);
|
|
503
560
|
return false;
|
|
504
561
|
}
|
|
505
562
|
const found = nonLiteralIn(value);
|
|
563
|
+
// tracker CONV-decode.ts:28
|
|
506
564
|
if (found)
|
|
507
|
-
say(at, found, 'non_literal_raw', `\`raw()\` on ${at.owner} holds ${describeIn(at, found)}. Write its item out with literal values
|
|
565
|
+
say(at, found, 'non_literal_raw', `\`raw()\` on ${at.owner} holds ${describeIn(at, found)}. Write its item out with literal values.`);
|
|
508
566
|
return !found;
|
|
509
567
|
}
|
|
510
568
|
/** Only BUILDERS: other runtime names (`Remove(…)`) pass the converter's parse, then its decode has no case for them. */
|
|
@@ -513,11 +571,12 @@ function isBuilderImported({ rulesApi, report }, callee) {
|
|
|
513
571
|
if (found?.kind === 'runtime' && found.imported === callee.name && BUILDERS.includes(callee.name))
|
|
514
572
|
return true;
|
|
515
573
|
const builder = found?.kind === 'runtime' ? found.imported : callee.name;
|
|
574
|
+
// tracker CONV-astValue.ts:296
|
|
516
575
|
report({ check: 'invalid_builder_import',
|
|
517
576
|
loc: callee.loc,
|
|
518
|
-
message:
|
|
577
|
+
message: BUILDERS.includes(builder)
|
|
519
578
|
? `\`${callee.name}(…)\` isn't the builder \`${builder}\` from ${RUNTIME_PACKAGE}. Import it under its own name, \`import { ${builder} } from '${RUNTIME_PACKAGE}'\`, and call it as \`${builder}(…)\`.`
|
|
520
|
-
: `\`${callee.name}(…)\` isn't a builder. In an attribute, only the builders of ${RUNTIME_PACKAGE} are called (${BUILDERS.join(', ')}); write a literal value, or one of them.`,
|
|
579
|
+
: `\`${callee.name}(…)\` isn't a builder. In an attribute, only the builders of ${RUNTIME_PACKAGE} are called (${BUILDERS.join(', ')}); write a literal value, or one of them.`,
|
|
521
580
|
});
|
|
522
581
|
return false;
|
|
523
582
|
}
|
|
@@ -532,25 +591,28 @@ function bindingNameImported({ node, rulesApi, report, context }, builder) {
|
|
|
532
591
|
const value = bare(named);
|
|
533
592
|
const what = builder === 'fn' ? "fn()'s function" : builder === 'callback' ? "callback()'s function" : "ctx()'s `from` hook";
|
|
534
593
|
if (value.type !== 'Identifier') {
|
|
535
|
-
|
|
594
|
+
// tracker CONV-astValue.ts:227
|
|
595
|
+
report({ check: 'non_identifier_binding_name', loc: value.loc, message: `${what} is \`${context.sourceCode.getText(value)}\`. Write an imported name: \`fn(numberToText, …)\`, \`{ from: useProduct }\`.` });
|
|
536
596
|
return null;
|
|
537
597
|
}
|
|
538
598
|
const found = rulesApi.getImport(value);
|
|
539
599
|
const problem = !found
|
|
540
|
-
?
|
|
600
|
+
? `${what} \`${value.name}\` isn't imported. Import it by name from its library: the function's library, or the context's app.`
|
|
541
601
|
: found.kind && RESERVED_FOR_BINDINGS.includes(found.kind)
|
|
542
|
-
?
|
|
602
|
+
? `${what} \`${value.name}\` comes from ${found.source}, which holds no functions or contexts. Import it from its own library: the function's library, or the context's app.`
|
|
543
603
|
: found.imported === '*'
|
|
544
|
-
?
|
|
604
|
+
? `${what} \`${value.name}\` is a namespace (\`import * as ${value.name}\`). Import the ${builder === 'fn' ? 'function' : 'hook'} itself by name: \`import { name } from '${found.source}'\`.`
|
|
545
605
|
: null;
|
|
606
|
+
// tracker CONV-astValue.ts:227; from a library that holds no functions or contexts: CONV-astValue.ts:230
|
|
546
607
|
if (problem)
|
|
547
|
-
report({ check: 'invalid_binding_import', loc: value.loc, message:
|
|
608
|
+
report({ check: 'invalid_binding_import', loc: value.loc, message: problem });
|
|
548
609
|
return found && !problem && builder === 'ctx' ? { hook: value, source: found.source } : null;
|
|
549
610
|
}
|
|
550
611
|
function ctxSourceHasName({ report }, { hook, source }) {
|
|
551
612
|
if (source.indexOf('/') !== source.length - 1)
|
|
552
613
|
return;
|
|
553
|
-
|
|
614
|
+
// tracker CONV-decode.ts:100
|
|
615
|
+
report({ check: 'missing_context_name', loc: hook.loc, message: `ctx()'s \`from\` hook \`${hook.name}\` is imported from '${source}', which has no context name after the \`/\`. Write '<componentType>/<contextName>'.` });
|
|
554
616
|
}
|
|
555
617
|
// ── reading the code ──
|
|
556
618
|
function getElementPropsEntries(element) {
|
|
@@ -656,9 +718,12 @@ function isWrappedByRuntime(element, rulesApi) {
|
|
|
656
718
|
const kind = rulesApi.getImport(element)?.kind;
|
|
657
719
|
return kind === 'wrapper' || kind === 'siteCode';
|
|
658
720
|
}
|
|
659
|
-
/** A callback prop written as an attribute with a call: `onClick={fn(goHome)}`. */
|
|
721
|
+
/** A callback prop written as an attribute with a call: `onClick={callback(goHome)}`, `onClick={fn(goHome)}`. */
|
|
660
722
|
function getCallbackAttributes(element) {
|
|
661
|
-
return element.openingElement.attributes.flatMap((attribute) =>
|
|
723
|
+
return element.openingElement.attributes.flatMap((attribute) => {
|
|
724
|
+
const value = attribute.type === 'JSXAttribute' && CALLBACK_PROP.test(getName(attribute)) ? getExpression(attribute) : null;
|
|
725
|
+
return value?.type === 'CallExpression' ? [[getName(attribute), attribute, value]] : [];
|
|
726
|
+
});
|
|
662
727
|
}
|
|
663
728
|
/** The callback props in `bindings={{ props: { onClick: … } }}`. */
|
|
664
729
|
function getBoundCallbacks(attribute) {
|
|
@@ -676,12 +741,14 @@ function runsAtRender(at, node) {
|
|
|
676
741
|
return RENDER_BUILDERS.includes(builder) ? `\`${builder}()\`` : null;
|
|
677
742
|
return isInvoke(node) || getParamReadKind(node) ? describeIn(at, node) : null;
|
|
678
743
|
}
|
|
679
|
-
/** The data attributes bound to a value the import reads as a binding;
|
|
744
|
+
/** The data attributes bound to a value the import reads as a binding; an `on…` attribute only with `callback()` (checkCallbackProps reports the rest). */
|
|
680
745
|
function boundAttributes(element, rulesApi) {
|
|
681
746
|
return element.openingElement.attributes.flatMap((attribute) => {
|
|
682
|
-
if (attribute.type !== 'JSXAttribute' || isReservedAttribute(getName(attribute))
|
|
747
|
+
if (attribute.type !== 'JSXAttribute' || isReservedAttribute(getName(attribute)))
|
|
683
748
|
return [];
|
|
684
749
|
const value = getExpression(attribute);
|
|
750
|
+
if (EVENT_ATTRIBUTE.test(getName(attribute)) && !(value && builderOf(value, rulesApi) === 'callback'))
|
|
751
|
+
return [];
|
|
685
752
|
return isBound(value) && !(value.type === 'CallExpression' && isSvgCall(value, rulesApi)) ? [[getName(attribute), attribute, value]] : [];
|
|
686
753
|
});
|
|
687
754
|
}
|
|
@@ -36,7 +36,7 @@ function isWellFormed(file, declaration, report) {
|
|
|
36
36
|
if (hasShape(declaration))
|
|
37
37
|
return true;
|
|
38
38
|
const params = `${declaration.name} ${declaration.query}`.trim();
|
|
39
|
-
reportCssProblem(report, file.name, declaration.line, 'malformed_custom_media', `\`@custom-media ${params}\` isn't \`--<name> <media query>\`; write it like \`@custom-media --mobile (max-width: 750px)
|
|
39
|
+
reportCssProblem(report, file.name, declaration.line, 'malformed_custom_media', `\`@custom-media ${params}\` isn't \`--<name> <media query>\`; write it like \`@custom-media --mobile (max-width: 750px);\`.`);
|
|
40
40
|
return false;
|
|
41
41
|
}
|
|
42
42
|
/** Reports a second `@custom-media` of a name the file already declares: the import can't tell two ranges with one name apart (CONV-breakpoints.ts:197). */
|
|
@@ -44,7 +44,7 @@ function isFirstOfName(file, declaration, first, report) {
|
|
|
44
44
|
const earlier = first.get(declaration.name);
|
|
45
45
|
if (!earlier)
|
|
46
46
|
return first.set(declaration.name, declaration), true;
|
|
47
|
-
reportCssProblem(report, file.name, declaration.line, 'duplicate_breakpoint', `\`@custom-media ${declaration.name}\` is declared again (first at ${file.name}:${earlier.line}). The import can't tell two ranges with one name apart: keep one
|
|
47
|
+
reportCssProblem(report, file.name, declaration.line, 'duplicate_breakpoint', `\`@custom-media ${declaration.name}\` is declared again (first at ${file.name}:${earlier.line}). The import can't tell two ranges with one name apart: keep one.`);
|
|
48
48
|
return false;
|
|
49
49
|
}
|
|
50
50
|
/** Reports a page file's declaration whose name isn't one of the site's (CONV-astParser.ts:335). */
|
|
@@ -52,7 +52,7 @@ function isSiteName(file, declaration, site, report) {
|
|
|
52
52
|
const names = site.breakpoints.map(({ name }) => name);
|
|
53
53
|
if (names.includes(declaration.name))
|
|
54
54
|
return true;
|
|
55
|
-
reportCssProblem(report, file.name, declaration.line, 'unknown_breakpoint_name', `\`@custom-media ${declaration.name}\` isn't one of the site's named breakpoints (the theme has ${names.join(', ') || 'none'}). Use a range the site declares; breakpoints are added in the editor, not in CSS
|
|
55
|
+
reportCssProblem(report, file.name, declaration.line, 'unknown_breakpoint_name', `\`@custom-media ${declaration.name}\` isn't one of the site's named breakpoints (the theme has ${names.join(', ') || 'none'}). Use a range the site declares; breakpoints are added in the editor, not in CSS.`);
|
|
56
56
|
return false;
|
|
57
57
|
}
|
|
58
58
|
/** Reports a page file's declaration whose range isn't the site's for that name, to the letter: the import compares the text (CONV-astParser.ts:339). */
|
|
@@ -60,7 +60,7 @@ function keepsSiteRange(file, declaration, site, report) {
|
|
|
60
60
|
const exported = getSiteRange(declaration.name, site);
|
|
61
61
|
if (exported.trim() === declaration.query.trim())
|
|
62
62
|
return true;
|
|
63
|
-
reportCssProblem(report, file.name, declaration.line, 'changed_breakpoint_range', `\`@custom-media ${declaration.name} ${declaration.query}\` changes the site's range for ${declaration.name}, which is \`${exported}\`. Restore it; a breakpoint's width is changed in the editor, not in CSS
|
|
63
|
+
reportCssProblem(report, file.name, declaration.line, 'changed_breakpoint_range', `\`@custom-media ${declaration.name} ${declaration.query}\` changes the site's range for ${declaration.name}, which is \`${exported}\`. Restore it; a breakpoint's width is changed in the editor, not in CSS.`);
|
|
64
64
|
return false;
|
|
65
65
|
}
|
|
66
66
|
/** Reports a `@media (--name)` with no declaration of that name in the same file (CONV-breakpoints.ts:57). A malformed declaration of the name (its own line) counts as the declaration, so the use gets no second line. */
|
|
@@ -73,7 +73,7 @@ function checkNamedUses(file, site, report) {
|
|
|
73
73
|
const fix = range
|
|
74
74
|
? `add \`@custom-media ${name} ${range};\` at the top of ${file.name}, or write the range itself, \`@media ${range}\``
|
|
75
75
|
: `add \`@custom-media ${name} (<its range>);\` at the top of ${file.name} (the range the site gives it), or write the range itself`;
|
|
76
|
-
reportCssProblem(report, file.name, line, 'undeclared_breakpoint', `\`@media (${name})\` uses a name the file doesn't declare: ${fix}
|
|
76
|
+
reportCssProblem(report, file.name, line, 'undeclared_breakpoint', `\`@media (${name})\` uses a name the file doesn't declare: ${fix}.`);
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
/** Reports each `@media` of a file whose range is none of the site's (P4-R01, CONV-breakpoints.ts:75); nothing without a theme to compare with. A name is read through the file's declaration when that declaration is the site's; otherwise the steps above report it. A component's own breakpoint (inside `@scope`) is compared once unmapped.json is read. */
|
|
@@ -86,7 +86,7 @@ function checkRanges(file, site, report) {
|
|
|
86
86
|
const query = resolve(media, file.declared, site);
|
|
87
87
|
if (query === null || site.breakpoints.some((breakpoint) => canonical(breakpoint.query) === canonical(query)))
|
|
88
88
|
continue;
|
|
89
|
-
reportCssProblem(report, file.name, media.line, 'unknown_breakpoint_range', `\`${describeAtRule({ name: 'media', params: media.query })}\` is a breakpoint the site doesn't have. Write one of the theme's breakpoints: ${describe(site.breakpoints)}
|
|
89
|
+
reportCssProblem(report, file.name, media.line, 'unknown_breakpoint_range', `\`${describeAtRule({ name: 'media', params: media.query })}\` is a breakpoint the site doesn't have. Write one of the theme's breakpoints: ${describe(site.breakpoints)}. The import refuses a value for a breakpoint the site doesn't have.`);
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
// ── reading the files ──
|