Merge remote-tracking branch 'origin/main' into merge-main

This commit is contained in:
Natalie Weizenbaum 2023-11-16 16:12:05 -08:00
commit d41ff1bb18
23 changed files with 883 additions and 233 deletions

View File

@ -13,7 +13,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with: {node-version: '${{ env.NODE_VERSION }}'}
- run: npm ci
- run: >
@ -28,7 +28,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with: {node-version: '${{ env.NODE_VERSION }}'}
- run: npm ci
- run: npm run toc-check
@ -39,7 +39,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with: {node-version: '${{ env.NODE_VERSION }}'}
- run: npm ci
- run: npm run link-check
@ -50,7 +50,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with: {node-version: '${{ env.NODE_VERSION }}'}
- run: npm ci
- run: npm run js-api-doc-check
@ -61,7 +61,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with: {node-version: '${{ env.NODE_VERSION }}'}
- run: npm ci
- run: npm run typedoc
@ -72,7 +72,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: bufbuild/buf-setup-action@v1.26.1
- uses: bufbuild/buf-setup-action@v1.28.0
with: {github_token: "${{ github.token }}"}
- name: Generate protobuf code
run: buf generate
@ -138,7 +138,7 @@ jobs:
- name: Find changed files in js-api-doc
id: changed-files
uses: tj-actions/changed-files@8238a4103220c636f2dad328ead8a7c8dbe316a3
uses: tj-actions/changed-files@25ef3926d147cd02fc7e931c1ef50772bbb0d25d
with: {files: js-api-doc}
- name: Deploy

View File

@ -134,7 +134,7 @@ The situations we chose to warn for are:
`futureDeprecations`.
In this scenario, the future deprecation will still be treated as fatal,
but we want to warn users to prevent situtations where a user tries to
but we want to warn users to prevent situations where a user tries to
make every deprecation fatal and ends up including future ones too.
* an obsolete deprecation is passed to `fatalDeprecations`.
@ -326,7 +326,7 @@ behavior of their global counterparts for compatibility reasons.
#### `slash-div`
Deprecation for treaing `/` as division.
Deprecation for treating `/` as division.
Update the proposal for forward slash as a separator to say that it emits
deprecation warnings with ID 'slash-div'.

View File

@ -1187,7 +1187,7 @@ Given a source file `file`, a [configuration](#configuration) `config`, and an
* Otherwise, let `scope` be the scope of the innermost block such that `scope`
already has a variable named `name`. Set `scope`'s variable `name` to `value`.
[scope]: ../spec/variables.md#scope
[scope]: ../spec/spec.md#scope
* When a top-level mixin or function declaration `declaration` is encountered:

View File

@ -125,7 +125,7 @@ To execute a `VariableDeclaration` `declaration`:
variable named `name`, set the innermost block's scope's variable `name` to
`value`.~~
[scope]: ../spec/variables.md#scope
[scope]: ../spec/spec.md#scope
* **Otherwise, if `resolved` is null, get the innermost block containing
`declaration` and set its scope's variable `name` to `value`.**

View File

@ -44,6 +44,11 @@ export interface CompileResult {
*
* This only allows synchronous {@link Importer}s and {@link CustomFunction}s.
*
* **Heads up!** When using the `sass-embedded` npm package,
* **{@link compileAsync} is almost always faster than {@link compile}**, due to
* the overhead of emulating synchronous messaging with worker threads and
* concurrent compilations being blocked on main thread.
*
* @example
*
* ```js
@ -66,9 +71,9 @@ export function compile(path: string, options?: Options<'sync'>): CompileResult;
* This only allows synchronous or asynchronous {@link Importer}s and
* {@link CustomFunction}s.
*
* **Heads up!** When using Dart Sass, **{@link compile} is almost twice as fast
* as {@link compileAsync}**, due to the overhead of making the entire
* evaluation process asynchronous.
* **Heads up!** When using the `sass` npm package, **{@link compile} is almost
* twice as fast as {@link compileAsync}**, due to the overhead of making the
* entire evaluation process asynchronous.
*
* @example
*
@ -94,6 +99,12 @@ export function compileAsync(
*
* This only allows synchronous {@link Importer}s and {@link CustomFunction}s.
*
* **Heads up!** When using the `sass-embedded` npm package,
* **{@link compileStringAsync} is almost always faster than
* {@link compileString}**, due to the overhead of emulating synchronous
* messaging with worker threads and concurrent compilations being blocked on
* main thread.
*
* @example
*
* ```js
@ -125,9 +136,9 @@ export function compileString(
* This only allows synchronous or asynchronous {@link Importer}s and {@link
* CustomFunction}s.
*
* **Heads up!** When using Dart Sass, **{@link compile} is almost twice as fast
* as {@link compileAsync}**, due to the overhead of making the entire
* evaluation process asynchronous.
* **Heads up!** When using the `sass` npm package, **{@link compileString} is
* almost twice as fast as {@link compileStringAsync}**, due to the overhead
* of making the entire evaluation process asynchronous.
*
* @example
*

View File

@ -96,6 +96,11 @@ export interface LegacyResult {
* This function synchronously compiles a Sass file to CSS. If it succeeds, it
* returns the result, and if it fails it throws an error.
*
* **Heads up!** When using the `sass-embedded` npm package, **{@link render}
* is almost always faster than {@link renderSync}**, due to the overhead of
* emulating synchronous messaging with worker threads and concurrent
* compilations being blocked on main thread.
*
* @example
*
* ```js
@ -116,9 +121,9 @@ export function renderSync(options: LegacyOptions<'sync'>): LegacyResult;
* `callback` with a {@link LegacyResult} if compilation succeeds or {@link
* LegacyException} if it fails.
*
* **Heads up!** When using Dart Sass, **{@link renderSync} is almost twice as
* fast as {@link render}** by default, due to the overhead of making the entire
* evaluation process asynchronous.
* **Heads up!** When using the `sass` npm package, **{@link renderSync} is
* almost twice as fast as {@link render}** by default, due to the overhead of
* making the entire evaluation process asynchronous.
*
* ```js
* const sass = require('sass'); // or require('node-sass');

View File

@ -1,3 +1,54 @@
## Draft 1.6
* Simplify the type definition for `interpolate`, and make `options` argument
optional.
* Fix typo in `changedValue` definition of `color.change`.
## Draft 1.5
* Clarify that deprecated SassColor getters (e.g. `red`, `blue`, etc.) convert
color to a legacy space before returning channel value.
## Draft 1.4
* In `change`, adjust algorithm for differentiating `hwb` from `hsl` when only
`hue` and no `space` is specified.
* In `change` for legacy colors, emit a `color-4-api` warning if a non-alpha
channel is explicitly null and no space is set.
* In procedure for Changing a Component Value, specify that `undefined` values
should return the `initialValue`.
* `toSpace` uses `Converting a Color` algorithm instead of `color.to-space()` to
avoid removing missing channels when converting to a legacy space.
* In `change` and constructors, throw an error for alpha and lightness values
that are out of range.
## Draft 1.3
* Rename new Embedded Protocol message from `SassColor` to `Color`.
* Make `color2` a positional parameter of `interpolate`, not an option.
* Add `rec2020` color space.
## Draft 1.2
* Add "alpha" to all channel name types.
* Remove `isAlphaMissing` in favor of `isChannelMissing("alpha")`.
* Rename types using title-case for acronyms longer than two letters in
camel-case identifiers (e.g. `ColorSpaceHsl` instead of `ColorSpaceHSL`).
* Remove generic `change` overload, and make `space` optional on others.
* Return `immutable` types for `channels` and `channelsOrNull`, and remove
assumption of 3 channels.
## Draft 1.1
* Clarify values in `channels` and `channelsOrNull`.

View File

@ -1,4 +1,4 @@
# CSS Color Level 4, New Color Spaces JavaScript API: Draft 1.1
# CSS Color Level 4, New Color Spaces JavaScript API: Draft 1.6
*([Issue](https://github.com/sass/sass/issues/2831),
[Changelog](color-4-new-spaces-js.changes.md))*
@ -24,7 +24,6 @@ proposal].
* [`channel`](#channel)
* [`alpha`](#alpha)
* [`isChannelMissing`](#ischannelmissing)
* [`isAlphaMissing`](#isalphamissing)
* [`isChannelPowerless`](#ischannelpowerless)
* [`interpolate`](#interpolate)
* [Updated Color Functions](#updated-color-functions)
@ -41,15 +40,18 @@ proposal].
* [Deprecations](#deprecations)
* [Procedures](#procedures)
* [Parsing a Channel Value](#parsing-a-channel-value)
* [Parsing a Clamped Channel Value](#parsing-a-clamped-channel-value)
* [Changing a Component Value](#changing-a-component-value)
* [Determining Construction Space](#determining-construction-space)
* [Embedded Protocol](#embedded-protocol)
* [SassColor](#sasscolor)
* [Color](#color)
* [Removed SassScript values](#removed-sassscript-values)
## API
```ts
import {List} from 'immutable';
import {Value} from '../spec/js-api/value';
```
@ -58,53 +60,54 @@ import {Value} from '../spec/js-api/value';
### Color Space Definitions
```ts
export type ColorSpaceHSL = 'hsl';
export type ColorSpaceHsl = 'hsl';
export type ChannelNameHSL = 'hue' | 'saturation' | 'lightness';
export type ChannelNameHsl = 'hue' | 'saturation' | 'lightness' | 'alpha';
export type ColorSpaceHWB = 'hwb';
export type ColorSpaceHwb = 'hwb';
export type ChannelNameHWB = 'hue' | 'whiteness' | 'blackness';
export type ChannelNameHwb = 'hue' | 'whiteness' | 'blackness' | 'alpha';
export type ColorSpaceLab = 'lab' | 'oklab';
export type ChannelNameLab = 'lightness' | 'a' | 'b';
export type ChannelNameLab = 'lightness' | 'a' | 'b' | 'alpha';
export type ColorSpaceLCH = 'lch' | 'oklch';
export type ColorSpaceLch = 'lch' | 'oklch';
export type ChannelNameLCH = 'lightness' | 'chroma' | 'hue';
export type ChannelNameLch = 'lightness' | 'chroma' | 'hue' | 'alpha';
export type ColorSpaceRGB =
export type ColorSpaceRgb =
| 'a98-rgb'
| 'display-p3'
| 'prophoto-rgb'
| 'rec2020'
| 'rgb'
| 'srgb'
| 'srgb-linear';
export type ChannelNameRGB = 'red' | 'green' | 'blue';
export type ChannelNameRgb = 'red' | 'green' | 'blue' | 'alpha';
export type ColorSpaceXYZ = 'xyz' | 'xyz-d50' | 'xyz-d65';
export type ColorSpaceXyz = 'xyz' | 'xyz-d50' | 'xyz-d65';
export type ChannelNameXYZ = 'x' | 'y' | 'z';
export type ChannelNameXyz = 'x' | 'y' | 'z' | 'alpha';
export type ChannelName =
| ChannelNameHSL
| ChannelNameHWB
| ChannelNameHsl
| ChannelNameHwb
| ChannelNameLab
| ChannelNameLCH
| ChannelNameRGB
| ChannelNameXYZ;
| ChannelNameLch
| ChannelNameRgb
| ChannelNameXyz;
export type KnownColorSpace =
| ColorSpaceHSL
| ColorSpaceHWB
| ColorSpaceHsl
| ColorSpaceHwb
| ColorSpaceLab
| ColorSpaceLCH
| ColorSpaceRGB
| ColorSpaceXYZ;
| ColorSpaceLch
| ColorSpaceRgb
| ColorSpaceXyz;
export type PolarColorSpace = ColorSpaceHSL | ColorSpaceHWB | ColorSpaceLCH;
export type PolarColorSpace = ColorSpaceHsl | ColorSpaceHwb | ColorSpaceLch;
export type RectangularColorSpace = Exclude<KnownColorSpace, PolarColorSpace>;
@ -135,13 +138,14 @@ get space(): KnownColorSpace;
* If `this.space` is equal to `space`, return `this`.
* Otherwise, return the result of [`color.to-space(internal, space)`].
* Otherwise, return the result of [Converting a Color] with `this` as
`origin-color` and `space` as `target-space`.
```ts
toSpace(space: KnownColorSpace): SassColor;
```
[`color.to-space(internal, space)`]: ./color-4-new-spaces.md#colorto-space
[Converting a Color]: ./color-4-new-spaces.md#converting-a-color
#### `isLegacy`
@ -177,14 +181,14 @@ toGamut(space?: KnownColorSpace): SassColor;
#### `channelsOrNull`
Returns an array of channel values (excluding alpha) for [`internal`], with
Returns a list of channel values (excluding alpha) for [`internal`], with
[missing channels][missing components] converted to `null`.
* Let `space` be the result of [`this.space`].
* Let `space` be the value of [`this.space`].
* Let `components` be the list of channels in `space`.
* Let `channels` be an empty array.
* Let `channels` be an empty list.
* For each `component` in `components`:
@ -197,7 +201,7 @@ Returns an array of channel values (excluding alpha) for [`internal`], with
* Return `channels`.
```ts
get channelsOrNull(): [number | null, number | null, number | null];
get channelsOrNull(): List<number | null>;
```
[missing components]: ./color-4-new-spaces.md#missing-components
@ -205,12 +209,12 @@ get channelsOrNull(): [number | null, number | null, number | null];
#### `channels`
This algorithm returns an array of channel values (excluding alpha) for
This algorithm returns a list of channel values (excluding alpha) for
[`internal`], with [missing channels][missing components] converted to `0`.
* Let `channelsOrNull` be the result of [`this.channelsOrNull`].
* Let `channelsOrNull` be the value of [`this.channelsOrNull`].
* Let `channels` be an empty array.
* Let `channels` be an empty list.
* For each `channel` in `channelsOrNull`:
@ -223,12 +227,12 @@ This algorithm returns an array of channel values (excluding alpha) for
[`this.channelsOrNull`]: #channelsornull
```ts
get channels(): [number, number, number];
get channels(): List<number>;
```
#### `channel`
* Let `initialSpace` be the value of [`this.space()`].
* Let `initialSpace` be the value of [`this.space`].
* Let `space` be `options.space` if it is defined, and the value of
`initialSpace` otherwise.
@ -239,36 +243,18 @@ get channels(): [number, number, number];
* Let `value` be the channel value in `color` with name of `component`.
* If `value` is null, return 0.
* If `value` is `null`, return 0.
* Otherwise, return `value`.
```ts
channel(channel: ChannelName): number;
channel(
channel: ChannelNameHSL | 'alpha',
options: {space: ColorSpaceHSL}
): number;
channel(
channel: ChannelNameHWB | 'alpha',
options: {space: ColorSpaceHWB}
): number;
channel(
channel: ChannelNameLab | 'alpha',
options: {space: ColorSpaceLab}
): number;
channel(
channel: ChannelNameLCH | 'alpha',
options: {space: ColorSpaceLCH}
): number;
channel(
channel: ChannelNameRGB | 'alpha',
options: {space: ColorSpaceRGB}
): number;
channel(
channel: ChannelNameXYZ | 'alpha',
options: {space: ColorSpaceXYZ}
): number;
channel(channel: ChannelNameHsl, options: {space: ColorSpaceHsl}): number;
channel(channel: ChannelNameHwb, options: {space: ColorSpaceHwb}): number;
channel(channel: ChannelNameLab, options: {space: ColorSpaceLab}): number;
channel(channel: ChannelNameLch, options: {space: ColorSpaceLch}): number;
channel(channel: ChannelNameRgb, options: {space: ColorSpaceRgb}): number;
channel(channel: ChannelNameXyz, options: {space: ColorSpaceXyz}): number;
```
#### `alpha`
@ -287,20 +273,11 @@ Returns the result of [`color.is-missing(internal,
channel)`][color.is-missing()] as a JavaScript boolean.
```ts
isChannelMissing(channel: ChannelName | 'alpha'): boolean;
isChannelMissing(channel: ChannelName): boolean;
```
[color.is-missing()]: ./color-4-new-spaces.md#coloris-missing-1
#### `isAlphaMissing`
Returns the result of [`color.is-missing(internal,
'alpha')`][color.is-missing()] as a JavaScript boolean.
```ts
get isAlphaMissing(): boolean;
```
#### `isChannelPowerless`
Returns the result of [`color.is-powerless(internal, channel, space)`] as a
@ -311,34 +288,34 @@ JavaScript boolean.
```ts
isChannelPowerless(channel: ChannelName): boolean;
isChannelPowerless(
channel: ChannelNameHSL,
options?: {space: ColorSpaceHSL}
channel: ChannelNameHsl,
options?: {space: ColorSpaceHsl}
): boolean;
isChannelPowerless(
channel: ChannelNameHWB,
options?: {space: ColorSpaceHWB}
channel: ChannelNameHwb,
options?: {space: ColorSpaceHwb}
): boolean;
isChannelPowerless(
channel: ChannelNameLab,
options?: {space: ColorSpaceLab}
): boolean;
isChannelPowerless(
channel: ChannelNameLCH,
options?: {space: ColorSpaceLCH}
channel: ChannelNameLch,
options?: {space: ColorSpaceLch}
): boolean;
isChannelPowerless(
channel: ChannelNameRGB,
options?: {space: ColorSpaceRGB}
channel: ChannelNameRgb,
options?: {space: ColorSpaceRgb}
): boolean;
isChannelPowerless(
channel: ChannelNameXYZ,
options?: {space: ColorSpaceXYZ}
channel: ChannelNameXyz,
options?: {space: ColorSpaceXyz}
): boolean;
```
#### `interpolate`
* Let `space` be the value of [`this.space()`].
* Let `space` be the value of [`this.space`].
* If `options.method` is set, let `interpolationMethod` be a space separated
list containing the value of `space`, a space, and the value of
@ -350,16 +327,16 @@ isChannelPowerless(
* Otherwise, let `interpolationMethod` be a space separated list containing the
value of `space`, a space, and the string "shorter".
* Return the result of [`color.mix(internal, options.color2, options.weight, interpolationMethod)`][`color.mix()`].
* Return the result of [`color.mix(internal, color2, options.weight, interpolationMethod)`][`color.mix()`].
```ts
interpolate(options: {color2: SassColor; weight?: number}): SassColor;
interpolate(options: {
color2: SassColor;
weight?: number;
method?: HueInterpolationMethod;
}): SassColor;
interpolate(
color2: SassColor,
options?: {
weight?: number;
method?: HueInterpolationMethod;
}
): SassColor;
```
[`color.mix()`]: ./color-4-new-spaces.md#colormix-1
@ -381,7 +358,7 @@ as the result of changing some of [`internal`]'s components.
> If `space` is not a [legacy color space], a channel value of `null` will
> result in a [missing component][missing components] value for that channel.
* Let `initialSpace` be the value of [`this.space()`].
* Let `initialSpace` be the value of [`this.space`].
* Let `spaceSetExplicitly` be `true` if `options.space` is defined, and `false`
otherwise.
@ -393,6 +370,9 @@ as the result of changing some of [`internal`]'s components.
* If `options.whiteness` or `options.blackness` is set, let `space` be `hwb`.
* Otherwise, if `options.hue` is set and `initialSpace` is `hwb`, let space be
`hwb`.
* Otherwise, if `options.hue`, `options.saturation`, or `options.lightness` is
set, let `space` be `hsl`.
@ -411,16 +391,25 @@ as the result of changing some of [`internal`]'s components.
* If any key in `keys` is not the name of a channel in `components`, throw an
error.
* If `options.alpha` is set, and isn't either null or a number between 0 and 1
(inclusive and fuzzy), throw an error.
* If `options.lightness` is set, and isn't either null or a number between 0 and
the maximum channel value for the space (inclusive and fuzzy), throw an error.
* Let `color` be the result of [`this.toSpace(space)`].
* Let `changedValue` be a function that takes a string argument for `channel`
and calls the procedure [`Changing a Component Value`] with `changes` and
`this` as `initial`.
`color` as `initial`.
* If `space` equals `hsl` and `spaceSetExplicitly` is `false`:
* If any of `options.hue`, `options.saturation`, `options.lightness` or
`options.alpha` equals null, emit a deprecation warning named `null-alpha`.
* If any of `options.hue`, `options.saturation` or `options.lightness` equals
`null`, emit a deprecation warning named `color-4-api`.
* If `options.alpha` equals `null`, emit a deprecation warning named
`null-alpha`.
* Let `changedColor` be the result of:
@ -449,8 +438,11 @@ as the result of changing some of [`internal`]'s components.
* If `space` equals `hwb` and `spaceSetExplicitly` is `false`:
* If any of `options.hue`, `options.whiteness`, `options.blackness` or
`options.alpha` equals null, emit a deprecation warning named `null-alpha`.
* If any of `options.hue`, `options.whiteness` or `options.blackness` equals
`null`, emit a deprecation warning named `color-4-api`.
* If `options.alpha` equals `null`, emit a deprecation warning named
`null-alpha`.
* Let `changedColor` be the result of:
@ -479,8 +471,11 @@ as the result of changing some of [`internal`]'s components.
* If `space` equals `rgb` and `spaceSetExplicitly` is `false`:
* If any of `options.red`, `options.green`, `options.blue` or `options.alpha`
equals null, emit a deprecation warning named `null-alpha`.
* If any of `options.red`, `options.green` or `options.blue` equals
`null`, emit a deprecation warning named `color-4-api`.
* If `options.alpha` equals `null`, emit a deprecation warning named
`null-alpha`.
* Let `changedColor` be the result of:
@ -531,8 +526,8 @@ as the result of changing some of [`internal`]'s components.
})
```
* If `space` equals `a98-rgb`, `display-p3`, `prophoto-rgb`, `srgb`, or
`srgb-linear`, let `changedColor` be the result of:
* If `space` equals `a98-rgb`, `display-p3`, `prophoto-rgb`, `rec2020`, `srgb`,
or `srgb-linear`, let `changedColor` be the result of:
```js
new SassColor({
@ -559,7 +554,6 @@ as the result of changing some of [`internal`]'s components.
* Return the result of [`changedColor.toSpace(initialSpace)`].
[`this.space()`]: #space
[`this.toSpace(space)`]: #tospace
[`changedColor.toSpace(initialSpace)`]: #tospace
[`Changing a Component Value`]: #changing-a-component-value
@ -567,25 +561,17 @@ as the result of changing some of [`internal`]'s components.
```ts
change(
options: {
[key in ChannelName]?: number | null;
} & {alpha?: number}
): SassColor;
change(
options: {
[key in ChannelNameHSL]?: number | null;
[key in ChannelNameHsl]?: number | null;
} & {
alpha?: number;
space: ColorSpaceHSL;
space?: ColorSpaceHsl;
}
): SassColor;
change(
options: {
[key in ChannelNameHWB]?: number | null;
[key in ChannelNameHwb]?: number | null;
} & {
alpha?: number;
space: ColorSpaceHWB;
space?: ColorSpaceHwb;
}
): SassColor;
@ -593,35 +579,31 @@ change(
options: {
[key in ChannelNameLab]?: number | null;
} & {
alpha?: number | null;
space: ColorSpaceLab;
space?: ColorSpaceLab;
}
): SassColor;
change(
options: {
[key in ChannelNameLCH]?: number | null;
[key in ChannelNameLch]?: number | null;
} & {
alpha?: number | null;
space: ColorSpaceLCH;
space?: ColorSpaceLch;
}
): SassColor;
change(
options: {
[key in ChannelNameRGB]?: number | null;
[key in ChannelNameRgb]?: number | null;
} & {
alpha?: number | null;
space: ColorSpaceRGB;
space?: ColorSpaceRgb;
}
): SassColor;
change(
options: {
[key in ChannelNameXYZ]?: number | null;
[key in ChannelNameXyz]?: number | null;
} & {
alpha?: number | null;
space: ColorSpaceXYZ;
space?: ColorSpaceXyz;
}
): SassColor;
```
@ -637,17 +619,21 @@ change(
#### Lab Channel Constructor
Create a new SassColor in a color space with Lab channels -- `lab` and `oklab`.
Create a new SassColor in a color space with Lab channels—`lab` and `oklab`.
* Let `lightness` be the result of [parsing a channel value] with value
`options.lightness`.
* If `options.space` equals `lab`, let `maximum` be `100`. Otherwise, let
`maximum` be `1`.
* Let `lightness` be the result of [parsing a clamped channel value] with
`value` of `options.lightness`, `minimum` of `0`, and `maximum` of `maximum`.
* Let `a` be the result of [parsing a channel value] with value `options.a`.
* Let `b` be the result of [parsing a channel value] with value `options.b`.
* If `options.alpha` is not set, let `alpha` be `1`. Otherwise, let `alpha` be
the result of [parsing a channel value] with value `options.alpha`.
the result of [parsing a clamped channel value] with value `options.alpha`,
`minimum` of 0, and `maximum` of 1.
* If `options.space` equals `lab`, set [`internal`] to the result of
[`lab(lightness a b / alpha)`].
@ -658,6 +644,7 @@ Create a new SassColor in a color space with Lab channels -- `lab` and `oklab`.
[`lab(lightness a b / alpha)`]: ./color-4-new-spaces.md#lab
[`oklab(lightness a b / alpha)`]: ./color-4-new-spaces.md#oklab
[parsing a channel value]: #parsing-a-channel-value
[parsing a clamped channel value]: #parsing-a-clamped-channel-value
```ts
constructor(options: {
@ -671,17 +658,21 @@ constructor(options: {
#### LCH Channel Constructor
Create a new SassColor in a color space with LCH channels -- `lch` and `oklch`.
Create a new SassColor in a color space with LCH channels—`lch` and `oklch`.
* Let `lightness` be the result of [parsing a channel value] with value
`options.lightness`.
* If `options.space` equals `lch`, let `maximum` be `100`. Otherwise, let
`maximum` be `1`.
* Let `lightness` be the result of [parsing a clamped channel value] with
`value` of `options.lightness`, `minimum` of `0`, and `maximum` of `maximum`.
* Let `c` be the result of [parsing a channel value] with value `options.c`.
* Let `h` be the result of [parsing a channel value] with value `options.h`.
* If `options.alpha` is not set, let `alpha` be `1`. Otherwise, let `alpha` be
the result of [parsing a channel value] with value `options.alpha`.
the result of [parsing a clamped channel value] with value `options.alpha`,
`minimum` of 0, and `maximum` of 1.
* If `options.space` equals `lch`, set [`internal`] to the result of
[`lch(lightness a b / alpha)`].
@ -698,14 +689,14 @@ constructor(options: {
chroma: number | null;
hue: number | null;
alpha?: number | null;
space: ColorSpaceLCH;
space: ColorSpaceLch;
});
```
#### Predefined RGB Channel Constructor
Create a new SassColor in a color space with RGB channels -- `srgb`,
`srgb-linear`, `display-p3`, `a98-rgb`, and `prophoto-rgb`. `rgb` is supported
Create a new SassColor in a color space with RGB channels—`srgb`, `srgb-linear`,
`display-p3`, `a98-rgb`, `prophoto-rgb`, and `rec2020`. `rgb` is supported
through the modified [RGB Constructor].
* Let `red` be the result of [parsing a channel value] with value `options.red`.
@ -717,7 +708,8 @@ through the modified [RGB Constructor].
`options.blue`.
* If `options.alpha` is not set, let `alpha` be `1`. Otherwise, let `alpha` be
the result of [parsing a channel value] with value `options.alpha`.
the result of [parsing a clamped channel value] with value `options.alpha`,
`minimum` of 0, and `maximum` of 1.
* Let `space` be the unquoted string value of `options.space`.
@ -732,14 +724,14 @@ constructor(options: {
green: number | null;
blue: number | null;
alpha?: number | null;
space: Exclude<ColorSpaceRGB, 'rgb'>;
space: Exclude<ColorSpaceRgb, 'rgb'>;
});
```
#### XYZ Channel Constructor
Create a new SassColor in a color space with XYZ channels -- `xyz`, `xyz-d50`,
and `xyz-d65`.
Create a new SassColor in a color space with XYZ channels—`xyz`, `xyz-d50`, and
`xyz-d65`.
* Let `x` be the result of [parsing a channel value] with value `options.x`.
@ -748,7 +740,8 @@ and `xyz-d65`.
* Let `z` be the result of [parsing a channel value] with value `options.z`.
* If `options.alpha` is not set, let `alpha` be `1`. Otherwise, let `alpha` be
the result of [parsing a channel value] with value `options.alpha`.
the result of [parsing a clamped channel value] with value `options.alpha`,
`minimum` of 0, and `maximum` of 1.
* Let `space` be the unquoted string value of `options.space`.
@ -762,7 +755,7 @@ constructor(options: {
y: number | null;
z: number | null;
alpha?: number | null;
space: ColorSpaceXYZ;
space: ColorSpaceXyz;
});
```
@ -784,11 +777,12 @@ Create a new SassColor in the `hsl` color space.
* Let `saturation` be the result of [parsing a channel value] with value
`options.saturation`.
* Let `lightness` be the result of [parsing a channel value] with value
`options.lightness`.
* Let `lightness` be the result of [parsing a clamped channel value] with
`value` of `options.lightness`, `minimum` of `0`, and `maximum` of `100`.
* If `options.alpha` is not set, let `alpha` be `1`. Otherwise, let `alpha` be
the result of [parsing a channel value] with value `options.alpha`.
the result of [parsing a clamped channel value] with `value` of
`options.alpha`, `minimum` of `0`, and `maximum` of `1`.
* Set [`internal`] to the result of [`hsl(hue saturation lightness / alpha)`].
@ -800,7 +794,7 @@ constructor(options: {
saturation: number | null;
lightness: number | null;
alpha?: number | null;
space?: ColorSpaceHSL;
space?: ColorSpaceHsl;
});
```
@ -820,7 +814,8 @@ Create a new SassColor in the `hwb` color space.
`options.blackness`.
* If `options.alpha` is not set, let `alpha` be `1`. Otherwise, let `alpha` be
the result of [parsing a channel value] with value `options.alpha`.
the result of [parsing a clamped channel value] with `value` of
`options.alpha`, `minimum` of `0`, and `maximum` of `1`.
* Set [`internal`] to the result of [`hwb(hue whiteness blackness / alpha)`].
@ -832,7 +827,7 @@ constructor(options: {
whiteness: number | null;
blackness: number | null;
alpha?: number | null;
space?: ColorSpaceHWB;
space?: ColorSpaceHwb;
});
```
@ -852,9 +847,10 @@ Create a new SassColor in the `rgb` color space.
`options.blue`.
* If `options.alpha` is not set, let `alpha` be `1`. Otherwise, let `alpha` be
the result of [parsing a channel value] with value `options.alpha`.
the result of [parsing a clamped channel value] with `value` of
`options.alpha`, `minimum` of `0`, and `maximum` of `1`.
* Return the result of [`rgb(red green blue / alpha)`].
* Set [`internal`] to the result of [`rgb(red green blue / alpha)`].
[`rgb(red green blue / alpha)`]: ./color-4-new-spaces.md#rgb-and-rgba
@ -878,17 +874,31 @@ A number of SassColor getters only make sense for [legacy color space], and so
are being deprecated in favor of the new [`channel`] function. This deprecation
is called `color-4-api`.
[`channel`]: #channel
The following deprecated getters return the result of
[`this.channel(channelName, { space: "rgb" })`][`channel`] where `channelName`
is the name of the respective getter.
* `red`
* `green`
* `blue`
The following deprecated getters return the result of
[`this.channel(channelName, { space: "hsl" })`][`channel`] where `channelName`
is the name of the respective getter.
* `hue`
* `saturation`
* `lightness`
The following deprecated getters return the result of
[`this.channel(channelName, { space: "hwb" })`][`channel`] where `channelName`
is the name of the respective getter.
* `whiteness`
* `blackness`
[`channel`]: #channel
## Procedures
### Parsing a Channel Value
@ -901,6 +911,18 @@ This procedure takes a channel value `value`, and returns the special value
* If `value` is the Javascript value `null`, return the unquoted Sass string
`none`.
### Parsing a Clamped Channel Value
This procedure takes a channel value `value` and an inclusive range of `minimum`
and `maximum`. It asserts the value is in the range, and returns the special
value `none` if the value is `null`.
* If `value` is fuzzy less-than `minimum`, throw an error.
* If `value` is fuzzy greater-than `maximum`, throw an error.
* Otherwise, return the result of [Parsing a Channel Value].
### Changing a Component Value
This procedure takes a `channel` name, an object `changes` and a SassColor
@ -911,7 +933,11 @@ This procedure takes a `channel` name, an object `changes` and a SassColor
* If `channel` is not a key in `changes`, return `initialValue`.
* Otherwise, return the value for `channel` in `changes`.
* Let `changedValue` be the value for `channel` in `changes`.
* If `changedValue` is `undefined` and not `null`, return `initialValue`.
* Otherwise, return `changedValue`.
### Determining Construction Space
@ -933,10 +959,10 @@ space for construction.
This introduces a breaking change in the Embedded Protocol, as it removes the
legacy SassScript values.
### SassColor
### Color
```proto
message SassColor {
message Color {
// The name of a known color space.
string space = 1;
@ -949,8 +975,7 @@ message SassColor {
// The value of the third channel associated with `space`.
double channel3 = 4;
// The color's alpha channel. Mandatory. Must be between 0 and 1,
// inclusive.
// The color's alpha channel. Mandatory. Must be between 0 and 1, inclusive.
double alpha = 5;
}
```

View File

@ -1,3 +1,12 @@
## Draft 1.3
* Handle empty subpath in "Resolving package exports" subprocedure.
## Draft 1.2
* Export `NodePackageImporter` type, and set `_NodePackageImporterBrand` to
unknown.
## Draft 1.1
* Throw an error if `nodePackageImporter` is used in the browser or other

View File

@ -1,4 +1,4 @@
# Package Importer
# Package Importer: Draft 1.3
*([Issue](https://github.com/sass/sass/issues/2739))*
@ -225,8 +225,8 @@ import {FileImporter, Importer} from '../spec/js-api/importer';
### `nodePackageImporter`
```ts
type NodePackageImporter = {
_NodePackageImporterBrand: any;
export type NodePackageImporter = {
_NodePackageImporterBrand: unknown;
};
export declare const nodePackageImporter: NodePackageImporter;
```
@ -440,7 +440,9 @@ This algorithm takes a package.json value `packageManifest`, a directory URL
* If `exports` is undefined, return null.
* Let `subpathVariants` be the result of [Export load paths] with `subpath`.
* If `subpath` is empty, let `subpathVariants` be an array with the string `.`.
Otherwise, let `subpathVariants` be the result of [Export load paths] with
`subpath`.
* Let `resolvedPaths` be a list of the results of calling
`PACKAGE_EXPORTS_RESOLVE(packageRoot, subpathVariant, exports, ["sass",

View File

@ -0,0 +1,12 @@
## Draft 1.2
* Await method calls in example code.
* Use `compiler.compile*()` methods in example code.
## Draft 1.1
* Remove unneeded returned/resolved value from `dispose`.
## Draft 1
* Initial draft

View File

@ -0,0 +1,226 @@
# Shared Resources in JavaScript API: Draft 1.1
*([Issue](https://github.com/sass/sass/issues/3296))*
This proposal adds an API design that allows for sharing resources across
multiple invocations of the Sass compiler's JavaScript API. This will provide
Sass's users with a more efficient way of running Sass compilations across
multiple files.
## Table of Contents
* [Summary](#summary)
* [Design Decisions](#design-decisions)
* [Splitting Sync and Async Compilers](#splitting-sync-and-async-compilers)
* [Limited API interface](#limited-api-interface)
* [Flexibility for interfaces on process management](#flexibility-for-interfaces-on-process-management)
* [No shared state](#no-shared-state)
* [Example](#example)
* [Sync Compiler](#sync-compiler)
* [Async Compiler](#async-compiler)
* [API](#api)
* [Types](#types)
* [initCompiler()](#initcompiler)
* [initAsyncCompiler()](#initasynccompiler)
* [Compiler](#compiler)
* [`compile()` and `compileString()`](#compile-and-compilestring)
* [dispose()](#dispose)
* [Async Compiler](#async-compiler-1)
* [`compileAsync()` and `compileStringAsync()`](#compileasync-and-compilestringasync)
* [dispose()](#dispose-1)
## Summary
Currently, the JavaScript API for Sass only accommodates a single compilation
per process. In practice, we have observed build tools are compiling multiple
times in response to a single user action. For instance, Vue.js authors using
Vite will see a Sass compilation for each `<style lang="scss">` tag that appears
in their codebase.
While processes can be spun up and down quickly, the combined time can add up to
a noticeable impact on performance. The embedded client supports long running
processes, and this proposal adds a method for the embedded host to manage the
lifecycle of these processes through a Compiler interface.
### Design Decisions
#### Splitting Sync and Async Compilers
When creating a Compiler, users will need to choose either a compiler that
provides access to synchronous or asynchronous compilation. While providing both
simultaneously from a single Compiler would offer more flexibility, it also adds
significant complexity to the API. In practice, we expect most users will only
want to use one mode, generally in the mode that is the fastest for the
implementation. If synchronous and asynchronous compilations are both needed,
users can create multiple Compilers.
#### Limited API interface
Many build tools allow passing the Sass module as a parameter, which offers
flexibility to users on what implementation of Sass is used. Because users may
still want to use portions of the JavaScript API unrelated to compilation, we
considered having the Compiler interface mirror the top level Sass interface,
which would allow users to replace instances of the imported `sass` class with
an instance of the compiler. However, this adds an additional cost to ongoing
Sass development. The proposed API does not eliminate this as a possibility in
the future.
#### Flexibility for interfaces on process management
In environments without access to a long-running compiler—for instance, the Dart
Sass implementation—the Compiler interface will continue to perform a single
compilation per process.
#### No shared state
This proposal does not change how a single compilation is done, and no state is
shared across compilations. Options and importers must be set for each
compilation. Future enhancements may introduce [shared state], but this proposal
only adds the ability to run multiple compilations on a single process.
[shared state]: https://github.com/sass/sass/issues/3296
This also means that the proposal makes no assertions about whether file content
has changed. It is also up to the user to determine when to start and stop a
long-running compiler process.
### Example
#### Sync Compiler
```js
import * as sass from 'sass';
function setup() {
const compiler = sass.initCompiler();
const result1 = compiler.compileString('a {b: c}').css;
const result2 = compiler.compileString('a {b: c}').css;
compiler.dispose();
// throws error
const result3 = compiler.compileString('a {b: c}').css;
}
```
#### Async Compiler
```js
import * as sass from 'sass';
async function setup() {
const compiler = await sass.initAsyncCompiler();
const result1 = await compiler.compileStringAsync('a {b: c}').css;
const result2 = await compiler.compileStringAsync('a {b: c}').css;
await compiler.dispose();
// throws error
const result3 = await compiler.compileStringAsync('a {b: c}').css;
}
```
## API
### Types
```ts
import {CompileResult} from '../spec/js-api/compile';
import {Options, StringOptions} from '../spec/js-api/options';
```
#### initCompiler()
Returns a synchronous [Compiler].
[Compiler]: #compiler
```ts
export function initCompiler(): Compiler;
```
#### initAsyncCompiler()
Resolves with an [Async Compiler].
[Async Compiler]: #async-compiler
```ts
export function initAsyncCompiler(): Promise<AsyncCompiler>;
```
#### Compiler
An instance of the synchronous Compiler.
```ts
export interface Compiler {
```
##### `compile()` and `compileString()`
Only synchronous compilation methods [`compile()`] and [`compileString()`] must be
included, and must have with identical semantics to the [Sass interface].
[`compile()`]: ../spec/js-api/compile.d.ts.md#compile
[`compilestring()`]: ../spec/js-api/compile.d.ts.md#compilestring
[Sass interface]: ../spec/js-api/index.d.ts.md
```ts
compile(path: string, options?: Options<'sync'>): CompileResult;
compileString(source: string, options?: StringOptions<'sync'>): CompileResult;
```
##### dispose()
When `dispose` is invoked on a Compiler:
* Any subsequent invocations of `compile` and `compileString` must throw an
error.
```ts
dispose(): void;
}
```
#### Async Compiler
An instance of the asynchronous Compiler.
```ts
export interface AsyncCompiler {
```
##### `compileAsync()` and `compileStringAsync()`
Only asynchronous compilation methods [`compileAsync()`] and
[`compileStringAsync()`] must be included, and must have with identical
semantics to the [Sass interface].
[`compileasync()`]: ../spec/js-api/compile.d.ts.md#compileasync
[`compilestringasync()`]: ../spec/js-api/compile.d.ts.md#compilestringasync
```ts
compileAsync(
path: string,
options?: Options<'async'>
): Promise<CompileResult>;
compileStringAsync(
source: string,
options?: StringOptions<'async'>
): Promise<CompileResult>;
```
##### dispose()
When `dispose` is invoked on an Async Compiler:
* Any subsequent invocations of `compileAsync` and `compileStringAsync` must
throw an error.
* Any compilations that have not yet been settled must be allowed to settle, and
not be cancelled.
* Resolves a Promise when all compilations have been settled, and disposal is
complete.
```ts
dispose(): Promise<void>;
}
```

View File

@ -53,15 +53,16 @@ To execute a `@for` rule `rule`:
* While `i` is not equal to `to`:
* Let `scope` be a new [scope].
* [In a new scope]:
* Add a variable with `rule`'s `VariableName` as its name and `i` as its value
to `scope`.
* Add a variable with `rule`'s `VariableName` as its name and `i` as its value
to the [current scope].
> Note that this variable will have the same unit that `from`.
* Execute the `ForBlock`'s statements in `scope`.
* Set `i` to `i + direction`.
> Note that this variable will have the same unit that `from`.
[scope]: ../variables.md#scope
* Execute the `ForBlock`'s statements.
* Set `i` to `i + direction`.
[In a new scope]: ../spec.md#running-in-a-new-scope
[current scope]: ../spec.md#scope

View File

@ -28,20 +28,38 @@ To execute a `@function` rule `rule`:
[vendor prefix]: ../syntax.md#vendor-prefix
* Let `parent` be the [current scope].
[current scope]: ../spec.md#scope
* Let `function` be a [function] named `name` which does the following when
executed with `args`:
[function]: ../types/functions.md
* With the current scope set to an empty [scope] with `parent` as its parent:
* Evaluate `args` with `rule`'s `ArgumentDeclaration`.
* Execute each statement in `rule`.
* Return the value from the `@return` rule if one was executed, or throw an
error if no `@return` rule was executed.
[scope]: ../spec.md#scope
* If `rule` is outside of any block of statements:
* If `name` *doesn't* begin with `-` or `_`, set [the current module][]'s
function `name` to `rule`.
function `name` to `function`.
> This overrides the previous definition, if one exists.
* Set [the current import context][]'s function `name` to `rule`.
* Set [the current import context]'s function `name` to `function`.
> This happens regardless of whether or not it begins with `-` or `_`.
[the current module]: ../spec.md#current-module
[the current import context]: ../spec.md#current-import-context
* Otherwise, set the innermost block's [scope][]'s function `name` to `value`.
[scope]: ../variables.md#scope
* Otherwise, set the [current scope]'s function `name` to `function`.

View File

@ -138,7 +138,7 @@ To execute an `@import` rule `rule`:
* Add `imported`'s [extensions][] to the current module.
* If the `@import` rule is nested within at-rules and/or style rules, add each
member in `imported` to the local [scope][].
member in `imported` to the [current scope].
* Otherwise, add each member in `imported` to the current import context and
the current module.
@ -158,4 +158,4 @@ To execute an `@import` rule `rule`:
[current import context]: ../spec.md#current-import-context
[resolving `imported`'s extensions]: extend.md#resolving-a-modules-extensions
[extensions]: extend.md#extension
[scope]: ../variables.md#scope
[current scope]: ../spec.md#scope

View File

@ -31,23 +31,41 @@ To execute a `@mixin` rule `rule`:
* Let `name` be the value of `rule`'s `Identifier`.
* Let `parent` be the [current scope].
[current scope]: ../spec.md#scope
* Let `mixin` be a [mixin] named `name` which accepts a content block if `rule`
contains a `@content` rule. To execute this mixin with `args`:
[mixin]: ../types/mixins.md
* With the current scope set to an empty [scope] with `parent` as its parent:
* Evaluate `args` with `rule`'s `ArgumentDeclaration`.
* Execute each statement in `rule`.
[scope]: ../spec.md#scope
* If `rule` is outside of any block of statements:
* If `name` *doesn't* begin with `-` or `_`, set [the current module][]'s
mixin `name` to `rule`.
* If `name` *doesn't* begin with `-` or `_`, set [the current module]'s
mixin `name` to `mixin`.
> This overrides the previous definition, if one exists.
* Set [the current import context][]'s mixin `name` to `rule`.
* Set [the current import context]'s mixin `name` to `mixin`.
> This happens regardless of whether or not it begins with `-` or `_`.
* Otherwise, set the innermost block's [scope][]'s mixin `name` to `value`.
[scope]: ../variables.md#scope
[the current module]: ../spec.md#current-module
[the current import context]: ../spec.md#current-import-context
* Otherwise, set the [current scope]'s mixin `name` to `mixin`.
> This overrides the previous definition, if one exists.
## `@include`
### Syntax
@ -70,15 +88,12 @@ To execute an `@include` rule `rule`:
* Let `name` be `rule`'s `NamespacedIdentifier`.
* Let `mixin` be the result of [resolving a mixin][] named `name`. If this
returns null, throw an error.
* Let `mixin` be the result of [resolving a mixin] named `name`. If this returns
null, throw an error.
[resolving a mixin]: ../modules.md#resolving-a-member
* Execute `rule`'s `ArgumentInvocation` with `mixin`'s `ArgumentDeclaration` in
`mixin`'s scope.
* Execute each statement in `mixin`.
* Execute `mixin` with `rule`'s `ArgumentInvocation`.
## `@content`

38
spec/built-in-modules.md Normal file
View File

@ -0,0 +1,38 @@
# Built-In Modules
Sass provides a number of built-in [modules] that may be loaded from URLs with
the scheme `sass`. These modules have no extensions, CSS trees, dependencies, or
source files. Their canonical URLs are the same as the URLs used to load them.
[modules]: modules.md#module
## Built-In Functions and Mixins
Each function and mixin defined in a built-in modules is specified with a
signature of the form
<x><pre>
[\<ident-token>] ArgumentDeclaration
</pre></x>
[\<ident-token>]: https://drafts.csswg.org/css-syntax-3/#ident-token-diagram
followed by a procedure. It's available as a member (either function or mixin)
in the module whose name is the value of the `<ident-token>`. When it's executed
with `args`:
* With an empty scope with no parent as the [current scope]:
[current scope]: spec.md#scope
* Evaluate `args` with the signature's `ArgumentDeclaration`.
* Run the procedure, and return its result if this is a function.
Built-in mixins don't accept content blocks unless explicitly specified
otherwise.
By convention, in these procedures `$var` is used as a shorthand for "the value
of the variable `var` in the current scope".
> In other words, `$var` is the value passed to the argument `$var`.

View File

@ -5,6 +5,7 @@ This built-in module is available from the URL `sass:meta`.
## Table of Contents
* [Functions](#functions)
* [`accepts-content()`](#accepts-content)
* [`calc-name()`](#calc-name)
* [`calc-args()`](#calc-args)
* [`call()`](#call)
@ -12,19 +13,36 @@ This built-in module is available from the URL `sass:meta`.
* [`feature-exists()`](#feature-exists)
* [`function-exists()`](#function-exists)
* [`get-function()`](#get-function)
* [`get-mixin()`](#get-mixin)
* [`global-variable-exists()`](#global-variable-exists)
* [`inspect()`](#inspect)
* [`keywords()`](#keywords)
* [`mixin-exists()`](#mixin-exists)
* [`module-functions()`](#module-functions)
* [`module-mixins()`](#module-mixins)
* [`module-variables()`](#module-variables)
* [`type-of()`](#type-of)
* [`variable-exists()`](#variable-exists)
* [Mixins](#mixins)
* [`apply()`](#apply)
* [`load-css()`](#load-css)
## Functions
### `accepts-content()`
This is a new function in the `sass:meta` module.
```
accepts-content($mixin)
```
* If `$mixin` is not a [mixin], throw an error.
[mixin]: ../types/mixins.md
* Return whether `$mixin` accepts a content block as a SassScript boolean.
### `calc-name()`
```
@ -90,6 +108,10 @@ This function is also available as a global function named `function-exists()`.
* If `$name` is not a string, throw an error.
* If `$name` is not an [`<ident-token>`], return false.
[`<ident-token>`]: https://drafts.csswg.org/css-syntax-3/#ident-token-diagram
* If `$module` is null:
* Return whether [resolving a function][] named `$name` returns null.
@ -117,6 +139,8 @@ This function is also available as a global function named `get-function()`.
* If `$name` is not a string, throw an error.
* If `$name` is not an [`<ident-token>`], throw an error.
* If `$module` is null:
* If `$css` is falsey:
@ -144,6 +168,33 @@ This function is also available as a global function named `get-function()`.
* Return [`use`'s module][]'s function named `$name`, or throw an error if no
such function exists.
### `get-mixin()`
```
get-mixin($name, $module: null)
```
* If `$name` is not a string, throw an error.
* If `$name` is not an [`<ident-token>`], throw an error.
* If `$module` is null:
* Return the result of [resolving a mixin] named `$name`. If this returns
null, throw an error.
[resolving a mixin]: ../modules.md#resolving-a-member
* Otherwise:
* If `$module` is not a string, throw an error.
* Let `use` be the `@use` rule in [the current source file] whose namespace is
equal to `$module`. If no such rule exists, throw an error.
* Return [`use`'s module]'s mixin named `$name`, or throw an error if no such
mixin exists.
### `global-variable-exists()`
```
@ -154,6 +205,10 @@ This function is also available as a global function named `global-variable-exis
* If `$name` is not a string, throw an error.
* If `$name` is not a [`PlainVariable`], return false.
[`PlainVariable`]: ../variables.md#syntax
* If `$module` is null:
* Return whether [resolving a variable][] named `$name`, ignoring local
@ -196,18 +251,18 @@ This function is also available as a global function named `mixin-exists()`.
* If `$name` is not a string, throw an error.
* If `$name` is not an [`<ident-token>`], return false.
* If `$module` is null:
* Return whether [resolving a mixin][] named `$name` returns null.
[resolving a mixin]: ../modules.md#resolving-a-member
* Return whether [resolving a mixin] named `$name` returns null.
* Otherwise, if `$module` isn't a string, throw an error.
* Otherwise, let `use` be the `@use` rule in [the current source file][] whose
* Otherwise, let `use` be the `@use` rule in [the current source file] whose
namespace is equal to `$module`. If no such rule exists, throw an error.
* Return whether [`use`'s module][] contains a mixin named `$name`.
* Return whether [`use`'s module] contains a mixin named `$name`.
### `module-functions()`
@ -225,6 +280,22 @@ This function is also available as a global function named `module-functions()`.
* Return a map whose keys are the names of functions in [`use`'s module][] and
whose values are the corresponding functions.
### `module-mixins()`
This is a new function in the `sass:meta` module.
```
module-mixins($module)
```
* If `$module` is not a string, throw an error.
* Let `use` be the `@use` rule in [the current source file] whose namespace is
equal to `$module`. If no such rule exists, throw an error.
* Return a map whose keys are the quoted string names of mixins in
[`use`'s module] and whose values are the corresponding mixins.
### `module-variables()`
```
@ -261,6 +332,7 @@ This function is also available as a global function named `type-of()`.
| Function | `"function"` |
| List | `"list"` |
| Map | `"map"` |
| Mixin | `"mixin"` |
| Null | `"null"` |
| Number | `"number"` |
| String | `"string"` |
@ -275,6 +347,8 @@ This function is also available as a global function named `variable-exists()`.
* If `$name` is not a string, throw an error.
* If `$name` is not a [`PlainVariable`], return false.
* If `$module` is null:
* Return whether [resolving a variable][] named `$name` returns null.
@ -288,6 +362,24 @@ This function is also available as a global function named `variable-exists()`.
## Mixins
### `apply()`
```
apply($mixin, $args...)
```
* If `$mixin` is not a [mixin], throw an error.
* If the current `@include` rule has a `ContentBlock` and `$mixin` doesn't
accept a block, throw an error.
* Execute `$mixin` with the `ArgumentInvocation` `(...$args)`. Treat the
`@include` rule that invoked `apply` as the `@include` rule that invoked
`$mixin`.
> This ensures that any `@content` rules in `$mixin` will use `apply()`'s
> `ContentBlock`.
### `load-css()`
```

View File

@ -426,13 +426,12 @@ and returns a member of type `type` or null.
* If `name` is a plain `Identifier` or a `Variable` that's not a
`NamespacedVariable`:
* Let `scope` be the [scope][] of the innermost block containing the current
statement such that `scope` has a member of type `type` named `name`, or
null if no such scope exists.
* Let `scope` be the [current scope] or its innermost parent such that `scope`
has a member of type `type` named `name`, or null if no such scope exists.
* If `scope` is not null, return `scope`'s value of type `type` named `name`.
[scope]: variables.md#scope
[current scope]: spec.md#scope
* If `name` is a [`NamespacedIdentifier`](#syntax) of the form
`namespace.raw-name` or a [`Variable`][] of the form `namespace.$raw-name`:

View File

@ -19,10 +19,14 @@ the language this way.
## Table of Contents
* [Definitions](#definitions)
* [Scope](#scope)
* [Global Scope](#global-scope)
* [Current Source File](#current-source-file)
* [Current Configuration](#current-configuration)
* [Current Import Context](#current-import-context)
* [Current Module](#current-module)
* [Procedures](#procedures)
* [Running in a New Scope](#running-in-a-new-scope)
* [Semantics](#semantics)
* [Compiling a Path](#compiling-a-path)
* [Compiling a String](#compiling-a-string)
@ -30,6 +34,23 @@ the language this way.
## Definitions
### Scope
A *scope* is a mutable structure that contains:
* The scope's *variables*: a mapping from identifiers to SassScript values.
* The scope's *mixins*: a mapping from identifiers to mixins.
* The scope's *functions*: a mapping from identifiers to functions.
* The scope's *parent*: a reference to another scope, which may be unset.
One scope at a time is designated the *current scope*. By default, this is the
[global scope](#global-scope).
### Global Scope
The *global scope* is the scope shared among the top level of all Sass files. It
has no parent.
### Current Source File
The *current source file* is the [source file][] that was passed to the
@ -64,6 +85,19 @@ invocation of [Executing a File](#executing-a-file).
> Because a module is only made immutable (other than its variables) when
> execution has finished, the current module is always mutable.
## Procedures
### Running in a New Scope
To run a set of steps *in a new scope*:
* Let `parent` be the [current scope].
[current scope]: #scope
* Return the result of running the given steps with the current scope set to an
empty scope with `parent` as its parent.
## Semantics
### Compiling a Path

62
spec/types/functions.md Normal file
View File

@ -0,0 +1,62 @@
# Functions
## Table of Contents
* [Types](#types)
* [Operations](#operations)
* [Equality](#equality)
* [Serialization](#serialization)
## Types
The value type known as a "function" is a procedure that takes an
`ArgumentInvocation` `args` and returns a SassScript value. Each function has a
string name.
> The specific details of executing this procedure differ depending on where and
> how the function is defined.
### Operations
A function follows the default behavior of all SassScript operations, except
that equality is defined as below.
#### Equality
Functions use reference equality: two function values are equal only if they
refer to the exact same instance of the same procedure.
> If the same file were to be imported multiple times, Sass would create a new
> function value for each `@function` rule each time the file is imported.
> Because a new function value has been created, although the name, body, and
> source span of a given function from the file would be the same between
> imports, the values would not be equal because they refer to different
> instances. Functions pre-defined by the Sass language are instatiated at most
> once during the entire evaluation of a program.
>
> As an example, if we declare two functions:
>
> ```scss
> @function foo() {
> @return red;
> }
>
> $a: meta.get-function(foo);
>
> @function foo {
> @return red;
> }
>
> $b: meta.get-function(foo);
> ```
>
> Although every aspect of the two functions is the same, `$a != $b`, because
> they refer to separate function values.
### Serialization
To serialize a function value:
* If the value is not being inspected, throw an error.
* Otherwise, emit `'get-function("'`, then the function's name, then `'")'`.

63
spec/types/mixins.md Normal file
View File

@ -0,0 +1,63 @@
# Mixins
## Table of Contents
* [Types](#types)
* [Operations](#operations)
* [Equality](#equality)
* [Serialization](#serialization)
## Types
The value type known as a "mixin" is a procedure that takes an
`ArgumentInvocation` `args` and returns nothing. Each mixin has a string name
and a boolean that indicates whether or not it accepts a content block.
> The specific details of executing this procedure differ depending on where and
> how the mixin is defined. A mixin will typically add nodes to the CSS
> stylesheet.
### Operations
A mixin follows the default behavior of all SassScript operations, except that
equality is defined as below.
#### Equality
Mixins use reference equality: two mixin values are equal only if they refer to
the exact same instance of the same procedure.
> If the same file were to be imported multiple times, Sass would create a new
> mixin value for each `@mixin` rule each time the file is imported. Because a
> new mixin value has been created, although the name, body, and source span of
> a given mixin from the file would be the same between imports, the values
> would not be equal because they refer to different instances. Mixins
> pre-defined by the Sass language are instatiated at most once during the
> entire evaluation of a program.
>
> As an example, if we declare two mixins:
>
> ```scss
> @mixin foo {
> color: red;
> }
>
> $a: meta.get-mixin(foo);
>
> @mixin foo {
> color: red;
> }
>
> $b: meta.get-mixin(foo);
> ```
>
> Although every aspect of the two mixins is the same, `$a != $b`, because they
> refer to separate mixin values.
### Serialization
To serialize a mixin value:
* If the value is not being inspected, throw an error.
* Otherwise, emit `'get-mixin("'`, then the mixin's name, then `'")'`.

View File

@ -3,9 +3,6 @@
## Table of Contents
* [Syntax](#syntax)
* [Definitions](#definitions)
* [Scope](#scope)
* [Global Scope](#global-scope)
* [Semantics](#semantics)
* [Executing a Variable Declaration](#executing-a-variable-declaration)
* [Evaluating a Variable](#evaluating-a-variable)
@ -27,18 +24,6 @@ the `.$` in `NamespacedVariable`. Each of `!global` and `!default` is allowed
at most once in `VariableDeclaration`. As with all statements, a
`VariableDeclaration` must be separated from other statements with a semicolon.
## Definitions
### Scope
A *scope* is a mapping from variable names to values. Every block of statements
delimited by `{` and `}` in SCSS or by indentation in the indented syntax has an
associated scope.
### Global Scope
The *global scope* is the scope shared among the top level of all Sass files.
## Semantics
### Executing a Variable Declaration
@ -107,8 +92,10 @@ To execute a `VariableDeclaration` `declaration`:
> This also overrides the previous definition.
* Otherwise, if `resolved` is null, get the innermost block containing
`declaration` and set its scope's variable `name` to `value`.
* Otherwise, if `resolved` is null, set the [current scope]'s variable `name` to
`value`.
[current scope]: spec.md#scope
* Otherwise, set `resolved`'s value to `value`.