Avoid adding duplicate modules when importing forwarded stylesheets
In Google stylesheets that imported import-only stylesheets with many
forwards many of times, we were seeing Environment._globalModules grow
large enough that variable accesses were a problem. This addresses
that in several ways:
* Forwarded modules are now ignored if the module is already being
forwarded.
* Module equality is more intelligent, so that shadowed and forwarded
modules can be effectively de-duplicated.
* If a module would be fully shadowed by a later import *and* that
module has no CSS, we no longer add an empty ShadowedModule to the
module list.
This combines all targets into a single extender invocation, which is
more efficient and allows it to more aggressively do redundant
selector elimination.
Prior to this, the watcher handled all the logic around recompiling
stylesheets if an upstream file was deleted or added in a way that
could affect their import resolution. This left a gap where the
stylesheet graph wouldn't be aware that a newly-added file had become
upstream dependency of an existing downstream file, leading to
recompilation failures.
This commit fixes that by moving all that logic into the stylesheet
graph. The graph now has full and sole responsibility for providing a
consistent view of which stylesheets depend on one another even as the
shape of the graph changes, and the watcher is just a client of that
logic.
Closes#550
We had been lazily initializing this to be more efficient when no
global modules were used, but this meant that the object wasn't shared
with closures created for mixins and functions that were created when
it was still `null`, so later imported forwards weren't visible to
those members.
When overloaded functions receive an incorrect number of positional
arguments, determine which overload has the most similar number of
arguments, and then correctly display that number in the error.
Closes#520sass/sass-spec#1496
For any @errors encountered in mixins or functions, use the call-site
(instead of the @error rule) as the span printed in the error message.
Closes#474
See sass/sass-spec#1494
In #827 I made configuration never be null, but this turns out to be
overkill: we still need a way for _loadModule() and _execute() to
distinguish between "use the existing configuration" (for @forward)
and "use no configuration" (for an unconfigured @use or
meta.load-module()). We now use null as a sentinel value there, while
still ensuring that _configuration is non-nullable.
Closes#854
We switched to *updating* selectors a while ago so that cloned rules
would continue to see updates in their selectors, but we were still
generating and tracking rules in the Extender. This caused skew
between what rules the extender knew about and what rules actually
existed in situations where rules were copied (such as when resolving
nested media queries).
There's no principled reason the extender needs to know about style
rules at all, so now it just tracks modifiable wrappers of selectors.
These are re-used even when style rules are cloned, so they're safe
from the skew problem.
Closes#843
Apparently npm and Yarn won't avoid installing a package version that
declares incompatibility with them, so dropping compatibility for an
old version is actually a breaking change 😭.
This avoids using maps and sets with custom equality, which are
moderately inefficient relative to built-in maps and sets on both Node
and the Dart VM. In order to make error messages continue to use the
original identifiers where possible, this updates some of them to rely
more heavily on their associated source spans for context.
We now wrap _withStackFrame() around wider sections of code, including
_loadStylesheet() which handles parse errors, so that the @use/@import
stack frames are available.
In addition to being useful for users of Sass, this will make it
possible for core Sass functions to produce warnings without needing
an explicit reference to the evaluator.
This function didn't expect a number ending in ".0", because they're
normally filtered out by fuzzyAsInt(). However, they can make it
through for very large doubles.
This doesn't have any code changes, but I want to verify that the new
deployment infrastructure works and the only way to do that is to
actually deploy.
The automatic @charset adding is useful in general, but there are
consistently cases where it trips up naïve downstream tools. This
option makes it easier for users to control when it occurs.
This adds a --no-unicode option to disable Unicode span rendering,
decouples repl highlighting from SourceSpan.highlight, and updates
tests to work with the new error highlighting.
It also tightly scopes source spans for statements with children.
Previously, source spans for these nodes extended all the way through
any whitespace that followed the node. This led to messy-looking
multiline span highlights with dart-lang/source_span#25.
Now, StylesheetParser.children doesn't consume trailing whitespace.
Instead, we add a helper method StylesheetParser._withChildren that
parses children, creates the appropriate span, and then consumes the
trailing whitespace.
Previously, evaluator called BinaryOperationExpression.span for each
binary operation it evaluated, which in turn called spanForList() to
create a span covering both child expressions. spanForList() then
called .span for both the left and right child operations *twice*,
leading to exponential behavior.
This is now avoided in three complementary ways:
1. The evaluator avoids eagerly calling AstNode.span, instead keeping
the original AstNode until the span itself needs to be accessed.
This means that a span will only be accessed when an error actually
occurs, and then only one operation's span will be accessed.
2. BinaryOperationExpression.span now iterates through any child
operations before calling their .span methods, so it only performs
O(1) allocations.
3. spanForList() now only calls each AstNode.span once.
This introduces two changes:
1. It changes the epsilon within which two numbers are considered
equal to be an order of magnitude smaller than the numeric
precision. Ruby Sass has always done this, and Dart Sass should
have but did not until now.
2. It parses the component of a number after the decimal point using
double.parse() to avoid accumulating floating point errors.
Always include the error location in JS error messages
I was trying to match Node Sass's behavior by having Error.formatted
property have more detail than Error.message, but our errors rely on
source snippets for context so this just ended up making them
confusing.
This allows us to accurately track the source spans for parenthesized
expressions, which in turn allows us to print accurate error
indications.
Adding a new class for this more accurately represents the structure
of the expression, but it also involves an extra allocation during
parsing and an extra level of nesting during evaluation which could
have a small but real performance impact.
We could alternatively add a package-internal setter for
Expression.span, and update the source span for parenthesized
expressions after they're initially parsed. However, this has its own
downsides: it adds complexity and mutability to the object model; and
many expression classes currently use lazily-generated spans, so
making them settable would require adding extra slots on those
classes.
I decided to go with the extra class because it only adds overhead
when parentheses are actually used in practice, as opposed to adding
overhead to every list/color/etc. The runtime overhead is also likely
to be mitigated if at any point we add a constant-folding step.
When a stylesheet is imported, the parsed stylesheet object is cached
based on its canonical URL. However, the stylesheet.span.sourceUrl was
based on the text of the import that was used to load that stylesheet.
The idea was to make the source URL in stack traces look nicer, but it
meant that relative URLs could be resolved based on the old importer's
URL before being sent to the new importer, which caused bugs.
Now stylesheet.span.sourceUrl is always the canonical URL of the
stylesheet, and thus safe to cache. We then use the import cache to
convert the canonical URL to a human-friendly URL at the point at
which we generate stack traces.
This also deprecates support for relative canonical URLs. The
semantics of these URLs were always unclear, and with the new change
in import internals the old behavior doesn't make much sense. It's
preserved for backwards-compatibility, but deprecated.
These codes *could* be supported on all Windows terminals, but
dart-lang/sdk#28614 means that they won't actually be recognized.
Partially addresses #395
If a non-partial stylesheet is next to a partial with the same name,
canonicalizing the source URL would fail. We don't really need to
canonicalize that URL anyway, though, since it's only used for import
loops and such a loop will reload and rerun the entrypoint anyway.
We were resolving URLs relative to the canonical URL rather than the
original URL, which broke importers for which those were different,
like the package importer.
Closes#334