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 43 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: 4 additions & 4 deletions lib/src/parse/at_root_query.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ class AtRootQueryParser extends Parser {
AtRootQuery parse() {
return wrapSpanFormatException(() {
scanner.expectChar($lparen);
whitespace();
whitespace(allowNewlines: true);
var include = scanIdentifier("with");
if (!include) expectIdentifier("without", name: '"with" or "without"');
whitespace();
whitespace(allowNewlines: true);
scanner.expectChar($colon);
whitespace();
whitespace(allowNewlines: true);

var atRules = <String>{};
do {
atRules.add(identifier().toLowerCase());
whitespace();
whitespace(allowNewlines: true);
} while (lookingAtIdentifier());
scanner.expectChar($rparen);
scanner.expectDone();
Expand Down
10 changes: 5 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(allowNewlines: true);

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

whitespace();
whitespace(allowNewlines: true);
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(allowNewlines: true);
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(allowNewlines: true);
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(allowNewlines: true);
} while (scanner.scanChar($comma));
scanner.expectChar($rparen);
}
Expand Down
4 changes: 2 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(allowNewlines: false);
jamesnw marked this conversation as resolved.
Show resolved Hide resolved
if (lookingAtIdentifier()) {
if (scanIdentifier("from")) {
selectors.add("from");
Expand All @@ -26,7 +26,7 @@ class KeyframeSelectorParser extends Parser {
} else {
selectors.add(_percentage());
}
whitespace();
whitespace(allowNewlines: false);
} while (scanner.scanChar($comma));
scanner.expectDone();

Expand Down
12 changes: 6 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(allowNewlines: true);
queries.add(_mediaQuery());
whitespace();
whitespace(allowNewlines: true);
} 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(allowNewlines: true);

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

whitespace();
whitespace(allowNewlines: true);
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(allowNewlines: true);
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(allowNewlines: true);

if (!scanIdentifier(operator)) return result;
expectWhitespace();
Expand Down
26 changes: 17 additions & 9 deletions lib/src/parse/parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,23 +67,29 @@ class Parser {
if (!scanner.scanChar($dollar)) return false;
if (!lookingAtIdentifier()) return false;
identifier();
whitespace();
whitespace(allowNewlines: false);
jamesnw marked this conversation as resolved.
Show resolved Hide resolved
return scanner.scanChar($colon);
}

// ## Tokens

/// Consumes whitespace, including any comments.
///
/// If [allowNewlines] is true, the indented syntax will consume newlines as
/// whitespace, in positions when a statement can not end.
jamesnw marked this conversation as resolved.
Show resolved Hide resolved
@protected
void whitespace() {
void whitespace({required bool allowNewlines}) {
jamesnw marked this conversation as resolved.
Show resolved Hide resolved
do {
whitespaceWithoutComments();
whitespaceWithoutComments(allowNewlines: allowNewlines);
} while (scanComment());
}

/// Consumes whitespace, but not comments.
///
/// If [allowNewlines] is true, the indented syntax will consume newlines as
/// whitespace, in positions when a statement can not end.
@protected
void whitespaceWithoutComments() {
void whitespaceWithoutComments({required bool allowNewlines}) {
while (!scanner.isDone && scanner.peekChar().isWhitespace) {
scanner.readChar();
}
Expand Down Expand Up @@ -116,13 +122,15 @@ class Parser {
}

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

whitespace();
whitespace(allowNewlines: allowNewlines);
}

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

whitespace();
whitespace(allowNewlines: 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 +415,7 @@ class Parser {
>= 0x0080:
buffer.writeCharCode(scanner.readChar());
case int(isWhitespace: true):
whitespace();
whitespace(allowNewlines: true);
if (scanner.peekChar() != $rparen) break loop;
case $rparen:
buffer.writeCharCode(scanner.readChar());
Expand Down
68 changes: 41 additions & 27 deletions lib/src/parse/sass.dart
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,19 @@ class SassParser extends StylesheetParser {
}

void expectStatementSeparator([String? name]) {
if (!atEndOfStatement()) _expectNewline();
if (!atEndOfStatement()) _expectNewline(canEndInSemicolon: true);
if (_peekIndentation() <= currentIndentation) return;
scanner.error(
"Nothing may be indented ${name == null ? 'here' : 'beneath a $name'}.",
position: _nextIndentationEnd!.position);
}

bool atEndOfStatement() => scanner.peekChar()?.isNewline ?? true;
bool atEndOfStatement() {
var next = scanner.peekChar();
return next.isNewline ||
next == null ||
jamesnw marked this conversation as resolved.
Show resolved Hide resolved
(next == $semicolon && scanner.peekChar(1).isNewline);
jamesnw marked this conversation as resolved.
Show resolved Hide resolved
}

bool lookingAtChildren() =>
atEndOfStatement() && _peekIndentation() > currentIndentation;
Expand Down Expand Up @@ -259,7 +264,7 @@ class SassParser extends StylesheetParser {
buffer.writeCharCode(scanner.readChar());
buffer.writeCharCode(scanner.readChar());
var span = scanner.spanFrom(start);
whitespace();
whitespace(allowNewlines: false);

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

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

void whitespaceWithoutComments() {
void whitespaceWithoutComments({required bool allowNewlines}) {
// This overrides whitespace consumption so that it doesn't consume
// newlines.
// newlines where that would cause a statement to end.
jamesnw marked this conversation as resolved.
Show resolved Hide resolved
while (!scanner.isDone) {
var next = scanner.peekChar();
if (next != $tab && next != $space) break;
if (!allowNewlines && !next.isSpaceOrTab) {
break;
} else if (allowNewlines && !next.isWhitespace) {
break;
}
jamesnw marked this conversation as resolved.
Show resolved Hide resolved
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() {
/// Expect and consume a single newline.
///
/// If [canEndInSemicolon] is true, this will also consume a `;` before the
/// newline if present.
void _expectNewline({bool canEndInSemicolon = false}) {
switch (scanner.peekChar()) {
case $semicolon
when canEndInSemicolon && [$lf, $ff].contains(scanner.peekChar(1)):
jamesnw marked this conversation as resolved.
Show resolved Hide resolved
scanner.readChar();
scanner.readChar();
return;
case $semicolon when canEndInSemicolon && scanner.peekChar(1) == $cr:
scanner.readChar();
scanner.readChar();
if (scanner.peekChar() == $lf) scanner.readChar();
return;
case $semicolon:
scanner.error("semicolons aren't allowed in the indented syntax.");
scanner.error(
"multiple statements on one line are not supported in the indented syntax.");
case $cr:
scanner.readChar();
if (scanner.peekChar() == $lf) scanner.readChar();
Expand Down Expand Up @@ -404,7 +411,14 @@ class SassParser extends StylesheetParser {
}

var start = scanner.state;
if (!scanCharIf((char) => char.isNewline)) {

jamesnw marked this conversation as resolved.
Show resolved Hide resolved
if (scanner.peekChar().isNewline) {
scanner.readChar();
} else if (scanner.peekChar() == $semicolon &&
scanner.peekChar(1).isNewline) {
scanner.readChar();
scanner.readChar();
} else {
scanner.error("Expected newline.", position: scanner.position);
}

Expand Down
20 changes: 10 additions & 10 deletions lib/src/parse/scss.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class ScssParser extends StylesheetParser {
Interpolation styleRuleSelector() => almostAnyValue();

void expectStatementSeparator([String? name]) {
whitespaceWithoutComments();
whitespaceWithoutComments(allowNewlines: true);
if (scanner.isDone) return;
if (scanner.peekChar() case $semicolon || $rbrace) return;
scanner.expectChar($semicolon);
Expand All @@ -38,7 +38,7 @@ class ScssParser extends StylesheetParser {

bool scanElse(int ifIndentation) {
var start = scanner.state;
whitespace();
whitespace(allowNewlines: true);
var beforeAt = scanner.state;
if (scanner.scanChar($at)) {
if (scanIdentifier('else', caseSensitive: true)) return true;
Expand All @@ -62,7 +62,7 @@ class ScssParser extends StylesheetParser {

List<Statement> children(Statement child()) {
scanner.expectChar($lbrace);
whitespaceWithoutComments();
whitespaceWithoutComments(allowNewlines: true);
var children = <Statement>[];
while (true) {
switch (scanner.peekChar()) {
Expand All @@ -73,17 +73,17 @@ class ScssParser extends StylesheetParser {
switch (scanner.peekChar(1)) {
case $slash:
children.add(_silentComment());
whitespaceWithoutComments();
whitespaceWithoutComments(allowNewlines: true);
case $asterisk:
children.add(_loudComment());
whitespaceWithoutComments();
whitespaceWithoutComments(allowNewlines: true);
default:
children.add(child());
}

case $semicolon:
scanner.readChar();
whitespaceWithoutComments();
whitespaceWithoutComments(allowNewlines: true);

case $rbrace:
scanner.expectChar($rbrace);
Expand All @@ -97,7 +97,7 @@ class ScssParser extends StylesheetParser {

List<Statement> statements(Statement? statement()) {
var statements = <Statement>[];
whitespaceWithoutComments();
whitespaceWithoutComments(allowNewlines: true);
while (!scanner.isDone) {
switch (scanner.peekChar()) {
case $dollar:
Expand All @@ -107,17 +107,17 @@ class ScssParser extends StylesheetParser {
switch (scanner.peekChar(1)) {
case $slash:
statements.add(_silentComment());
whitespaceWithoutComments();
whitespaceWithoutComments(allowNewlines: true);
case $asterisk:
statements.add(_loudComment());
whitespaceWithoutComments();
whitespaceWithoutComments(allowNewlines: true);
default:
if (statement() case var child?) statements.add(child);
}

case $semicolon:
scanner.readChar();
whitespaceWithoutComments();
whitespaceWithoutComments(allowNewlines: true);

default:
if (statement() case var child?) statements.add(child);
Expand Down
Loading
Loading