Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor to include parseMediaQuery util for @csstools/media-query-list-parser #6559

Closed
mattxwang opened this issue Jan 5, 2023 · 6 comments · Fixed by #6585
Closed

Refactor to include parseMediaQuery util for @csstools/media-query-list-parser #6559

mattxwang opened this issue Jan 5, 2023 · 6 comments · Fixed by #6585
Labels
status: ready to implement is ready to be worked on by someone type: refactor an improvement to the code structure

Comments

@mattxwang
Copy link
Member

Necessary context: PR #6550 introduces @csstools/media-query-list-parser to v15 as a parser for media queries. In thread #6550 (comment), @ybiquitous and @romainmenke discuss the best way to catch errors from the parser.  To thread the relevant portions:

const mediaQueryList = parse(atRule.params);

@ybiquitous: Is it possible to recover the error like the parseSelector() utility?

@romainmenke: This is not simple because the parser is forgiving and doesn't throw whenever it encounters something unexpected.
... (more in thread)
Invalid media queries can not have a specific error messages because these are unknowns.
We only know that the component values do not fit the known grammar.
It is possible to emit a warning with failed to parse X but it is not possible to explain why it failed to parse it.

Ideally we have good examples of error cases and the messages we want for each.
Then this work can be done once for all rules that would use this parser :)

@ybiquitous: we may have the following utility for other rules using @csstools/media-query-list-parser (this PR doesn't need to add it, though)

// lib/utils/parseMediaQuery.js

/**
 * @param {import('postcss').AtRule} atRule
 * @param {import('stylelint').PostcssResult} result
 * @returns {ReturnType<typeof parse>}
 */
module.exports = function parseMediaQuery(atRule, result) {
	const source = atRule.params;
	return parse(source, {
		onParseError: (err) => {
			result.warn(`Cannot parse selector (${err})`, {
				node: atRule,
				word: source,
				stylelintType: 'parseError',
			});
		},
		preserveInvalidMediaQueries: true,
	});
};

From my understanding, we should then be able to use this snippet in rules like media-feature-name-allowed-list (and other ones, such as a proposed media-feature-no-invalid-range). To move forward, I think we would want to finalize:

  • is this necessary?
  • what should the proper error message be? what information should we pass to developers, plugin authors, etc.
  • what edge cases are we most interested in testing for?

I will be honest and say that I don't have great familiarity with either @csstools/media-query-list-parser nor the complexities of media query parsing. That being said, I'm certainly happy to do any of the involved coding or testing work: writing the util, writing tests for the util, and using it to subsume any existing code.

What do we think / have I missed anything?

@mattxwang mattxwang added status: needs discussion triage needs further discussion type: refactor an improvement to the code structure labels Jan 5, 2023
@romainmenke
Copy link
Member

romainmenke commented Jan 5, 2023

Tokenizer parse errors :

  • Reverse solidus (\) does not start a valid escape sequence
  • Unexpected EOF in a comment /* a comment without trailer, at end of file
  • Unexpected EOF in an escape sequence
  • Unexpected EOF in a string token "string without end quote, at end of file
  • Unexpected Newline in a string token
  • Unexpected EOF in a url token
  • Unexpected characters in a url token
  • Invalid escape sequences in a url token

I think all of these are highly technical and if anything should report these to CSS authors it would be better if it was a dedicated rule.


Component value parse errors :

  • Unexpected EOF in a function token (calc(10px * 2 missing trailing ))
  • Unexpected EOF in a simple block token ([value="10" missing trailing ])
  • Expected EOF but found a token after parsing a component value (only one component value is expected, but more tokens were found)

A parser error however does not mean that something will be broken.
Everything just works when you omit ).

<div style="width: calc(10px * 2"></div>

Screenshot 2023-01-05 at 08 56 27

These are also highly technical and rarely relevant to a specific rule.


Invalid media queries :

preserveInvalidMediaQueries: true

Any media query that has custom (or future) grammar will fail to parse.
These are not parser errors, they are valid CSS syntax, but invalid media queries.


Maybe this type of utility makes the most sense :

  • ignore tokenizer errors
  • ignore component value errors
  • report invalid media queries
/**
 * @param {import('postcss').AtRule} atRule
 * @param {import('stylelint').PostcssResult} result
 * @returns {ReturnType<typeof parse>}
 */
module.exports = function parseMediaQuery(atRule, result) {
	const source = atRule.params;
	const mediaQueries = parse(source, {
		preserveInvalidMediaQueries: true,
	});

	mediaQueries.forEach((mediaQuery) => {
		if (isMediaQueryInvalid(mediaQuery)) {
			result.warn(`Cannot parse media query`, {
				node: atRule,
				word: source, // TODO : set correct values
				stylelintType: 'parseError',
			});
		}
	})

	return mediaQueries
};

Invalid media queries can still be walked through but they won't be media queries, they will only contain component values.

This setup makes it possible to visit all calc() functions regardless of the validity of the media queries.

In practice you will rarely have to think about this detail.
Either you are trying to visit a Media Feature with a range context which is only possible in a valid media query. Or you are trying to visit a component value (e.g. calc()) and the validity of the media query is irrelevant.

@ybiquitous
Copy link
Member

@mattxwang Thanks for opening the issue.

@romainmenke Thanks for the feedback.

One question. Is returning invalid mediaQuery elements reasonable? Will developers write code more easily by returning filtered elements like this?

return mediaQueries.filter((mediaQuery) => {
	if (isMediaQueryInvalid(mediaQuery)) {
		result.warn(`Cannot parse media query`, {
			node: atRule,
			word: source, // TODO : set correct values
			stylelintType: 'parseError',
		});

		return false;
	}

	return true;
});

@romainmenke
Copy link
Member

One question. Is returning invalid mediaQuery elements reasonable? Will developers write code more easily by returning filtered elements like this?

This would break things for users in rules that fix CSS.

@media invalid, screen and (width: 10px) {}

After serializing it would become :

@media screen and (width: 10rem) {}

@ybiquitous
Copy link
Member

I see. Filtering is unnecessary. 👍🏼

@jeddy3 jeddy3 removed the type: refactor an improvement to the code structure label Jan 5, 2023
@jeddy3
Copy link
Member

jeddy3 commented Jan 19, 2023

It sounds like we're going with the util outlined in #6559 (comment). I'll label the issue as ready to go.

@jeddy3 jeddy3 changed the title Add parseMediaQuery util for @csstools/media-query-list-parser Refactor to include parseMediaQuery util for @csstools/media-query-list-parser Jan 19, 2023
@jeddy3 jeddy3 added status: ready to implement is ready to be worked on by someone type: refactor an improvement to the code structure and removed status: needs discussion triage needs further discussion labels Jan 19, 2023
@ybiquitous ybiquitous linked a pull request Jan 22, 2023 that will close this issue
@ybiquitous
Copy link
Member

Closed via #6585

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: ready to implement is ready to be worked on by someone type: refactor an improvement to the code structure
Development

Successfully merging a pull request may close this issue.

4 participants