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 64 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
2c4a054
Support optional semicolons at the end of indented lines
jamesnw Nov 1, 2024
94f8c11
Merge branch 'main' of https://github.com/sass/dart-sass into indente…
jamesnw Nov 25, 2024
4d0e43c
Add comment, update error message
jamesnw Nov 27, 2024
dc12326
Handle each
jamesnw Nov 27, 2024
332ca09
Allow whitespace in @for, handle initial whitespace for at-rules indi…
jamesnw Dec 2, 2024
3715ee4
@content, @extend newlines
jamesnw Dec 2, 2024
8594fe5
@input newlines
jamesnw Dec 2, 2024
512304b
Whitespace for @mixin, @include
jamesnw Dec 2, 2024
c09e506
Whitespace in @media, @-moz-document, @supports
jamesnw Dec 2, 2024
1fa6ccf
Whitespace in @use and @forward
jamesnw Dec 2, 2024
9e49c3b
Whitespace in @function and @return
jamesnw Dec 2, 2024
939d2fa
Whitespace in @if
jamesnw Dec 3, 2024
d050c01
Whitespace in @while
jamesnw Dec 3, 2024
e8cf0fb
Whitespace in @at-root
jamesnw Dec 3, 2024
b6c801a
Merge branch 'main' into indented-syntax-improvements
jamesnw Dec 4, 2024
0aaa63a
Whitespace in arguments and @charset
jamesnw Dec 4, 2024
cfa30ee
+ and = syntax whitespace
jamesnw Dec 4, 2024
e6442d4
Whitespace in variable declarations
jamesnw Dec 4, 2024
0d81850
Move initial whitespace inside each at rule
jamesnw Dec 4, 2024
2a3c160
Whitespace in lists and maps
jamesnw Dec 5, 2024
65ec459
Handle whitespace in important and unaries
jamesnw Dec 5, 2024
7de5da3
Whitespace in interpolation, unblock expressionUntilComma issues
jamesnw Dec 5, 2024
85d14a9
Consume whitespace in sub-stylesheet parsers
jamesnw Dec 6, 2024
dac97ed
Merge branch 'main' of https://github.com/sass/dart-sass into indente…
jamesnw Dec 6, 2024
61304e6
Consume whitespace in parse/css. Meaningless, as it extends scss
jamesnw Dec 6, 2024
d7ee188
Whitespace in @supports
jamesnw Dec 6, 2024
976e38a
Whitespace in @media
jamesnw Dec 6, 2024
a7f439c
Handle whitespace in arg invocations
jamesnw Dec 6, 2024
14dbd8d
Whitespace in parsers
jamesnw Dec 6, 2024
c552cdd
Allow newlines in comments
jamesnw Dec 12, 2024
adc94db
Newlines in selector parse
jamesnw Dec 13, 2024
114cfa6
Require whitespace argument
jamesnw Dec 13, 2024
7e58b2d
Allow whitespace inside url parens
jamesnw Dec 13, 2024
78bd20f
Cleanup
jamesnw Dec 13, 2024
afdd5ec
Merge branch 'main' of https://github.com/sass/dart-sass into indente…
jamesnw Dec 13, 2024
13c42b2
Handle newlines in parentheses in supports conditions
jamesnw Dec 16, 2024
61b4137
Allow newlines in brackets and parens in selectors
jamesnw Dec 16, 2024
0843edd
Documentation, review
jamesnw Dec 16, 2024
5e880e3
More docs
jamesnw Dec 16, 2024
1ec13e5
Rename consumeNewlines to allowNewlines
jamesnw Dec 16, 2024
5e3db80
Fix bug in semicolons in sass
jamesnw Dec 17, 2024
08747a9
Merge branch 'main' of https://github.com/sass/dart-sass into indente…
jamesnw Dec 17, 2024
38e05bf
Merge branch 'main' of https://github.com/sass/dart-sass into indente…
jamesnw Dec 20, 2024
ba62f15
Use local whitespace functions where value is irrelevant
jamesnw Dec 30, 2024
38ea36b
Apply code suggestions to sass.dart
jamesnw Dec 30, 2024
6d69938
Allow whitespace after debug, error, warn
jamesnw Dec 30, 2024
b99d3ac
Revise whitespace in if, function and supports conditions
jamesnw Dec 30, 2024
8ad5805
Don't break inside brackets in declarations
jamesnw Dec 30, 2024
ce9522b
Whitespace in destructuring each
jamesnw Dec 30, 2024
f0f8c7a
Disallow whitespace after @charset, document _expression arg
jamesnw Dec 30, 2024
233d774
Add changelog
jamesnw Dec 31, 2024
aad4b5c
Errors on unmatched brackets
jamesnw Dec 31, 2024
cf711de
Allow whitespace after comma in memberlist
jamesnw Dec 31, 2024
f093d6a
Support whitespace and comments after semicolon
jamesnw Jan 2, 2025
efb0d4d
Exclude semicolons and comments from spans
jamesnw Jan 2, 2025
66838ca
Docs
jamesnw Jan 2, 2025
1ff011a
allowNewlines > consumeNewlines
jamesnw Jan 2, 2025
99311ad
Update pubspec to pass tests
jamesnw Jan 2, 2025
4000989
Merge branch 'main' of https://github.com/sass/dart-sass into indente…
jamesnw Jan 3, 2025
29f76df
Revisions
jamesnw Jan 3, 2025
43ee929
Consume newlines in expressionUntilComma in parentheses and maps
jamesnw Jan 3, 2025
ea8b098
Update comments
jamesnw Jan 3, 2025
3a50b08
Merge branch 'main' of https://github.com/sass/dart-sass into indente…
jamesnw Jan 6, 2025
523e38b
Style changes
nex3 Jan 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 1.84.0-dev

* Allow newlines in whitespace in the indented syntax.

* **Potentially breaking bug fix**: Selectors with unmatched brackets now always
produce a parser error. Previously, some edge cases like `[foo#{"]:is(bar"}) {a:
b}` would compile without error, but this was an unintentional bug.

## 1.83.1

* Fix a bug where `--quiet-deps` would get deactivated for `@content` blocks,
Expand Down
13 changes: 9 additions & 4 deletions lib/src/parse/at_root_query.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,27 @@ class AtRootQueryParser extends Parser {
AtRootQuery parse() {
return wrapSpanFormatException(() {
scanner.expectChar($lparen);
whitespace();
_whitespace();
var include = scanIdentifier("with");
if (!include) expectIdentifier("without", name: '"with" or "without"');
whitespace();
_whitespace();
scanner.expectChar($colon);
whitespace();
_whitespace();

var atRules = <String>{};
do {
atRules.add(identifier().toLowerCase());
whitespace();
_whitespace();
} while (lookingAtIdentifier());
scanner.expectChar($rparen);
scanner.expectDone();

return AtRootQuery(atRules, include: include);
});
}

/// The value of `consumeNewlines` is not relevant for this class.
void _whitespace() {
whitespace(consumeNewlines: true);
}
}
15 changes: 10 additions & 5 deletions lib/src/parse/css.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class CssParser extends ScssParser {
var start = scanner.state;
scanner.expectChar($at);
var name = interpolatedIdentifier();
whitespace();
_whitespace();

return switch (name.asPlain) {
"at-root" ||
Expand Down Expand Up @@ -113,7 +113,7 @@ class CssParser extends ScssParser {
.text
};

whitespace();
_whitespace();
var modifiers = tryImportModifiers();
expectStatementSeparator("@import rule");
return ImportRule(
Expand All @@ -126,7 +126,7 @@ class CssParser extends ScssParser {
// evaluation time.
var start = scanner.state;
scanner.expectChar($lparen);
whitespace();
_whitespace();
var expression = expressionUntilComma();
scanner.expectChar($rparen);
return ParenthesizedExpression(expression, scanner.spanFrom(start));
Expand All @@ -151,7 +151,7 @@ class CssParser extends ScssParser {
var arguments = <Expression>[];
if (!scanner.scanChar($rparen)) {
do {
whitespace();
_whitespace();
if (allowEmptySecondArg &&
arguments.length == 1 &&
scanner.peekChar() == $rparen) {
Expand All @@ -160,7 +160,7 @@ class CssParser extends ScssParser {
}

arguments.add(expressionUntilComma(singleEquals: true));
whitespace();
_whitespace();
} while (scanner.scanChar($comma));
scanner.expectChar($rparen);
}
Expand All @@ -180,4 +180,9 @@ class CssParser extends ScssParser {
var expression = super.namespacedExpression(namespace, start);
error("Module namespaces aren't allowed in plain CSS.", expression.span);
}

/// The value of `consumeNewlines` is not relevant for this class.
void _whitespace() {
whitespace(consumeNewlines: true);
}
}
9 changes: 7 additions & 2 deletions lib/src/parse/keyframe_selector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class KeyframeSelectorParser extends Parser {
return wrapSpanFormatException(() {
var selectors = <String>[];
do {
whitespace();
_whitespace();
if (lookingAtIdentifier()) {
if (scanIdentifier("from")) {
selectors.add("from");
Expand All @@ -26,7 +26,7 @@ class KeyframeSelectorParser extends Parser {
} else {
selectors.add(_percentage());
}
whitespace();
_whitespace();
} while (scanner.scanChar($comma));
scanner.expectDone();

Expand Down Expand Up @@ -71,4 +71,9 @@ class KeyframeSelectorParser extends Parser {
buffer.writeCharCode($percent);
return buffer.toString();
}

/// The value of `consumeNewlines` is not relevant for this class.
void _whitespace() {
whitespace(consumeNewlines: true);
}
}
17 changes: 11 additions & 6 deletions lib/src/parse/media_query.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ class MediaQueryParser extends Parser {
return wrapSpanFormatException(() {
var queries = <CssMediaQuery>[];
do {
whitespace();
_whitespace();
queries.add(_mediaQuery());
whitespace();
_whitespace();
} while (scanner.scanChar($comma));
scanner.expectDone();
return queries;
Expand All @@ -30,7 +30,7 @@ class MediaQueryParser extends Parser {
// This is somewhat duplicated in StylesheetParser._mediaQuery.
if (scanner.peekChar() == $lparen) {
var conditions = [_mediaInParens()];
whitespace();
_whitespace();

var conjunction = true;
if (scanIdentifier("and")) {
Expand All @@ -57,7 +57,7 @@ class MediaQueryParser extends Parser {
}
}

whitespace();
_whitespace();
if (!lookingAtIdentifier()) {
// For example, "@media screen {"
return CssMediaQuery.type(identifier1);
Expand All @@ -70,7 +70,7 @@ class MediaQueryParser extends Parser {
// For example, "@media screen and ..."
type = identifier1;
} else {
whitespace();
_whitespace();
modifier = identifier1;
type = identifier2;
if (scanIdentifier("and")) {
Expand Down Expand Up @@ -102,7 +102,7 @@ class MediaQueryParser extends Parser {
var result = <String>[];
while (true) {
result.add(_mediaInParens());
whitespace();
_whitespace();

if (!scanIdentifier(operator)) return result;
expectWhitespace();
Expand All @@ -117,4 +117,9 @@ class MediaQueryParser extends Parser {
scanner.expectChar($rparen);
return result;
}

/// The value of `consumeNewlines` is not relevant for this class.
void _whitespace() {
whitespace(consumeNewlines: true);
}
}
29 changes: 20 additions & 9 deletions lib/src/parse/parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,23 +67,31 @@ class Parser {
if (!scanner.scanChar($dollar)) return false;
if (!lookingAtIdentifier()) return false;
identifier();
whitespace();
whitespace(consumeNewlines: true);
return scanner.scanChar($colon);
}

// ## Tokens

/// Consumes whitespace, including any comments.
///
/// If [consumeNewlines] is `true`, the indented syntax will consume newlines
/// as whitespace. It should only be set to `true` in positions when a
/// statement can't end.
@protected
void whitespace() {
void whitespace({required bool consumeNewlines}) {
do {
whitespaceWithoutComments();
whitespaceWithoutComments(consumeNewlines: consumeNewlines);
} while (scanComment());
}

/// Consumes whitespace, but not comments.
///
/// If [consumeNewlines] is `true`, the indented syntax will consume newlines
/// as whitespace. It should only be set to `true` in positions when a
/// statement can't end.
@protected
void whitespaceWithoutComments() {
void whitespaceWithoutComments({required bool consumeNewlines}) {
while (!scanner.isDone && scanner.peekChar().isWhitespace) {
scanner.readChar();
}
Expand Down Expand Up @@ -116,13 +124,16 @@ class Parser {
}

/// Like [whitespace], but throws an error if no whitespace is consumed.
///
/// If [consumeNewlines] is `true`, the indented syntax will consume newlines
/// as whitespace. It should only be set to `true` in positions when a
/// statement can't end.
@protected
void expectWhitespace() {
void expectWhitespace({bool consumeNewlines = false}) {
if (scanner.isDone || !(scanner.peekChar().isWhitespace || scanComment())) {
scanner.error("Expected whitespace.");
}

whitespace();
whitespace(consumeNewlines: consumeNewlines);
}

/// Consumes and ignores a single silent (Sass-style) comment, not including
Expand Down Expand Up @@ -386,7 +397,7 @@ class Parser {
return null;
}

whitespace();
whitespace(consumeNewlines: true);

// Match Ruby Sass's behavior: parse a raw URL() if possible, and if not
// backtrack and re-parse as a function expression.
Expand All @@ -407,7 +418,7 @@ class Parser {
>= 0x0080:
buffer.writeCharCode(scanner.readChar());
case int(isWhitespace: true):
whitespace();
whitespace(consumeNewlines: true);
if (scanner.peekChar() != $rparen) break loop;
case $rparen:
buffer.writeCharCode(scanner.readChar());
Expand Down
55 changes: 28 additions & 27 deletions lib/src/parse/sass.dart
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ class SassParser extends StylesheetParser {
}

void expectStatementSeparator([String? name]) {
if (!atEndOfStatement()) _expectNewline();
var trailingSemicolon = _tryTrailingSemicolon();
if (!atEndOfStatement()) {
_expectNewline(trailingSemicolon: trailingSemicolon);
}
if (_peekIndentation() <= currentIndentation) return;
scanner.error(
"Nothing may be indented ${name == null ? 'here' : 'beneath a $name'}.",
Expand Down Expand Up @@ -259,7 +262,7 @@ class SassParser extends StylesheetParser {
buffer.writeCharCode(scanner.readChar());
buffer.writeCharCode(scanner.readChar());
var span = scanner.spanFrom(start);
whitespace();
whitespace(consumeNewlines: false);

// For backwards compatibility, allow additional comments after
// the initial comment is closed.
Expand All @@ -269,7 +272,7 @@ class SassParser extends StylesheetParser {
_expectNewline();
}
_readIndentation();
whitespace();
whitespace(consumeNewlines: false);
}

if (!scanner.isDone && !scanner.peekChar().isNewline) {
Expand Down Expand Up @@ -309,37 +312,22 @@ class SassParser extends StylesheetParser {
return LoudComment(buffer.interpolation(scanner.spanFrom(start)));
}

void whitespaceWithoutComments() {
// This overrides whitespace consumption so that it doesn't consume
// newlines.
void whitespaceWithoutComments({required bool consumeNewlines}) {
// This overrides whitespace consumption to only consume newlines when
// `consumeNewlines` is true.
while (!scanner.isDone) {
var next = scanner.peekChar();
if (next != $tab && next != $space) break;
if (consumeNewlines ? !next.isWhitespace : !next.isSpaceOrTab) break;
scanner.readChar();
}
}

void loudComment() {
// This overrides loud comment consumption so that it doesn't consume
// multi-line comments.
scanner.expect("/*");
while (true) {
var next = scanner.readChar();
if (next.isNewline) scanner.error("expected */.");
if (next != $asterisk) continue;

do {
next = scanner.readChar();
} while (next == $asterisk);
if (next == $slash) break;
}
}

/// Expect and consume a single newline character.
void _expectNewline() {
///
/// If [trailingSemicolon] is true, this follows a semicolon, which is used
/// for error reporting.
void _expectNewline({bool trailingSemicolon = false}) {
switch (scanner.peekChar()) {
case $semicolon:
scanner.error("semicolons aren't allowed in the indented syntax.");
case $cr:
scanner.readChar();
if (scanner.peekChar() == $lf) scanner.readChar();
Expand All @@ -348,7 +336,9 @@ class SassParser extends StylesheetParser {
scanner.readChar();
return;
default:
scanner.error("expected newline.");
scanner.error(trailingSemicolon
? "multiple statements on one line are not supported in the indented syntax."
: "expected newline.");
}
}

Expand Down Expand Up @@ -467,4 +457,15 @@ class SassParser extends StylesheetParser {
position: scanner.position - scanner.column, length: scanner.column);
}
}

/// Consumes a semicolon and trailing whitespace, including comments.
///
/// Returns whether a semicolon was consumed.
bool _tryTrailingSemicolon() {
if (scanCharIf((char) => char == $semicolon)) {
whitespace(consumeNewlines: false);
return true;
}
return false;
}
}
Loading