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

[Indented syntax improvements] Dart implementation #2467

Open
wants to merge 58 commits into
base: main
Choose a base branch
from

Conversation

jamesnw
Copy link
Contributor

@jamesnw jamesnw commented Dec 16, 2024

Blocked until proposal is accepted.

[skip sass-embedded]

@jamesnw
Copy link
Contributor Author

jamesnw commented Dec 16, 2024

@nex3 This is ready for review. Here are some questions I wanted to flag as you review. Thanks!

  1. allowNewlines is set to true in some cases, like in parser* functions or in scss.dart, where the value is non-impactful. Should this usage be differentiated somehow for clarity?
  2. There are some places, like in _atRootQuery, where allowNewlines is true because it is inside parentheses. I opted to not use the _inParentheses variable except for one place where the consumption did rely on it, in _supportsCondition. Would it be more idiomatic to use _inParentheses, even if the consumption is in the same function?
  3. In the + include syntax, I am not supporting whitespace after the + as it can be part of a selector. This differs from the = mixin syntax- should they be the same?
  4. I added paren/bracket context to almostAnyValue, with the only change being that they must be matched. That means the error for a:has(d[)] will be changed from "Expected identifer" to "Expected ]". Since almostAnyValue is used for more than selectors, should I make this functionality opt-in, dependent on a parameter? Based on the places where this is used, I don't foresee issues with bracket matching, but want to verify.

Copy link
Contributor

@nex3 nex3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nex3 This is ready for review. Here are some questions I wanted to flag as you review. Thanks!

  1. allowNewlines is set to true in some cases, like in parser* functions or in scss.dart, where the value is non-impactful. Should this usage be differentiated somehow for clarity?

For classes where it's universally non-impactful, I'd define a local _whitespace() function that takes no arguments with a comment explaining that the value isn't relevant.

  1. There are some places, like in _atRootQuery, where allowNewlines is true because it is inside parentheses. I opted to not use the _inParentheses variable except for one place where the consumption did rely on it, in _supportsCondition. Would it be more idiomatic to use _inParentheses, even if the consumption is in the same function?

I think how it is now is better.

  1. In the + include syntax, I am not supporting whitespace after the + as it can be part of a selector. This differs from the = mixin syntax- should they be the same?

There's already an odd inconsistency here where whitespace is allowed after = but not after +, so I think it's fine to expand that to allowing newlines as well. It's possible that we should deprecate that, but it's not in-scope for this change.

  1. I added paren/bracket context to almostAnyValue, with the only change being that they must be matched. That means the error for a:has(d[)] will be changed from "Expected identifer" to "Expected ]". Since almostAnyValue is used for more than selectors, should I make this functionality opt-in, dependent on a parameter? Based on the places where this is used, I don't foresee issues with bracket matching, but want to verify.

It's fine to change this error. Arguably the bracket error is clearer anyway.

lib/src/parse/keyframe_selector.dart Outdated Show resolved Hide resolved
lib/src/parse/parser.dart Outdated Show resolved Hide resolved
lib/src/parse/parser.dart Outdated Show resolved Hide resolved
lib/src/parse/sass.dart Outdated Show resolved Hide resolved
lib/src/parse/sass.dart Outdated Show resolved Hide resolved
allowNewlines: allowNewlines ||
bracketList ||
_inParentheses ||
wasInExpression);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this trigger on wasInExpression?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In reducing it to a minimal reproduction, this covers newlines after a value in a comma separated list that has a space separated list as a value. (This is a common syntax in grid and font declarations, for instance.)

For instance, this would be covered by wasInParentheses.

$a: (b, e
  )

However, this would not be covered by wasInParentheses, but is covered by wasInExpression.

$a: (b c, e
  )

Is there a cleaner way of doing this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you pass consumeNewlines: true into expressionUntilComma() in parentheses(), I think that's a much more explicit way of representing the logic that parenthesized lists should allow newlines everywhere. That should allow you to remove the _inParentheses check as well.

lib/src/parse/stylesheet.dart Outdated Show resolved Hide resolved
lib/src/parse/stylesheet.dart Outdated Show resolved Hide resolved
lib/src/parse/stylesheet.dart Outdated Show resolved Hide resolved
lib/src/parse/stylesheet.dart Outdated Show resolved Hide resolved
Copy link
Contributor Author

@jamesnw jamesnw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nex3 This is ready for another round of review, along with the spec draft 1.2. Thanks!

allowNewlines: allowNewlines ||
bracketList ||
_inParentheses ||
wasInExpression);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In reducing it to a minimal reproduction, this covers newlines after a value in a comma separated list that has a space separated list as a value. (This is a common syntax in grid and font declarations, for instance.)

For instance, this would be covered by wasInParentheses.

$a: (b, e
  )

However, this would not be covered by wasInParentheses, but is covered by wasInExpression.

$a: (b c, e
  )

Is there a cleaner way of doing this?

@@ -1064,7 +1074,7 @@ abstract class StylesheetParser extends Parser {
ImportRule _importRule(LineScannerState start) {
var imports = <Import>[];
do {
whitespace();
whitespace(allowNewlines: false);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain more what needs to be changed? As far as I can tell, allowNewlines is false throughout the import rule.

lib/src/parse/stylesheet.dart Outdated Show resolved Hide resolved
lib/src/parse/stylesheet.dart Outdated Show resolved Hide resolved
lib/src/parse/stylesheet.dart Outdated Show resolved Hide resolved
lib/src/parse/stylesheet.dart Outdated Show resolved Hide resolved
lib/src/parse/stylesheet.dart Outdated Show resolved Hide resolved
switch (scanner.peekChar()) {
case $semicolon
when canEndInSemicolon && [$lf, $ff].contains(scanner.peekChar(1)):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed my approach in f093d6a. I'd appreciate feedback on this approach.

Now, in the indented format, a call to expectStatementSeparator() consumes ; // foo before checking if atEndOfStatement(). This deviates from SCSS, which consumes only the semicolon, but we need to move the scanner position to immediately before the newline in order to peekIndentation().

This required some minor tweaks to @debug, for example, so that the scanner range matches SCSS, and doesn't include ; // foo.

lib/src/parse/parser.dart Outdated Show resolved Hide resolved
lib/src/parse/sass.dart Outdated Show resolved Hide resolved
## 1.84.0-dev

* Allow newlines in whitespace in the indented syntax.
* **Potentially breaking bug fix**: Selectors with interpolations that render
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the term "render" is confusing here, because the issue is that the brackets are unmatched at the source level.

return scanner.scanChar($colon);
}

// ## Tokens

/// Consumes whitespace, including any comments.
///
/// If [consumeNewlines] is true, the indented syntax will consume newlines as
/// whitespace in positions when a statement can't end.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is ambiguous between "consumeNewlines: true should only be passed in positions where a statement can't end" and "this function will automatically ignore consumeNewlines: true in positions where a statement can end". Probably worth rephrasing it to be explicit.

// This overrides whitespace consumption so that it doesn't consume
// newlines.
// newlines where that would cause a statement to end.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly to above, I think it makes more sense to phrase this in terms of the function's interface, so something like "when consumeNewlines is false."

@@ -404,6 +394,7 @@ class SassParser extends StylesheetParser {
}

var start = scanner.state;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: churn

/// Consumes a semicolon and trailing whitespace, including comments.
///
/// Returns whether a semicolon was consumed.
bool _trailingSemicolon() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally if a production may or may not consume something and returns whether it did, we use a name that starts with "try".

allowNewlines: allowNewlines ||
bracketList ||
_inParentheses ||
wasInExpression);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you pass consumeNewlines: true into expressionUntilComma() in parentheses(), I think that's a much more explicit way of representing the logic that parenthesized lists should allow newlines everywhere. That should allow you to remove the _inParentheses check as well.

case $rparen || $rbracket:
if (brackets.isEmpty) {
scanner.error(
'Unexpected "${String.fromCharCode(scanner.peekChar()!)}".');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, you can write case ($paren || $rbracket) && var char and it'll bind it to char.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants