Make the new JS API a bit more idiomatic (#3200)

This takes more advantage of overloads and unions rather than
using factory constructors or methods with different names.
This commit is contained in:
Natalie Weizenbaum 2021-11-29 16:10:42 -08:00 committed by GitHub
parent 4939fb6cff
commit ebbc52877a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 243 additions and 248 deletions

View File

@ -1,26 +1,26 @@
import {Value} from './index';
export class SassColor extends Value {
static rgb(
red: number,
green: number,
blue: number,
alpha?: number
): SassColor;
constructor(options: {
red: number;
green: number;
blue: number;
alpha?: number;
});
static hsl(
hue: number,
saturation: number,
lightness: number,
alpha?: number
): SassColor;
constructor(options: {
hue: number;
saturation: number;
lightness: number;
alpha?: number;
});
static hwb(
hue: number,
whiteness: number,
blackness: number,
alpha?: number
): SassColor;
constructor(options: {
hue: number;
whiteness: number;
blackness: number;
alpha?: number;
});
get red(): number;
@ -40,26 +40,24 @@ export class SassColor extends Value {
get alpha(): number;
changeRgb(options: {
change(options: {
red?: number;
green?: number;
blue?: number;
alpha?: number;
}): SassColor;
changeHsl(options: {
change(options: {
hue?: number;
saturation?: number;
lightness?: number;
alpha?: number;
}): SassColor;
changeHwb(options: {
change(options: {
hue?: number;
whiteness?: number;
blackness?: number;
alpha?: number;
}): SassColor;
changeAlpha(alpha: number): SassColor;
}

View File

@ -1,4 +1,4 @@
import {List, OrderedMap, ValueObject} from 'immutable';
import {List, ValueObject} from 'immutable';
import {SassBoolean} from './boolean';
import {SassColor} from './color';
@ -34,6 +34,8 @@ export abstract class Value implements ValueObject {
sassIndexToListIndex(sassIndex: Value, name?: string): number;
get(index: number): Value | undefined;
assertBoolean(name?: string): SassBoolean;
assertColor(name?: string): SassColor;
@ -46,7 +48,7 @@ export abstract class Value implements ValueObject {
assertString(name?: string): SassString;
tryMap(): OrderedMap<Value, Value> | null;
tryMap(): SassMap | null;
equals(other: Value): boolean;

View File

@ -10,17 +10,11 @@ export class SassList extends Value {
options?: {
/** @default ',' */
separator?: ListSeparator;
/** @default false */
brackets?: boolean;
}
);
static empty(options?: {
/** @default null */
separator?: ListSeparator;
/** @default false */
brackets?: boolean;
}): SassList;
constructor(options?: {separator?: ListSeparator; brackets?: boolean});
get separator(): ListSeparator;
}

View File

@ -1,13 +1,16 @@
import {OrderedMap} from 'immutable';
import {SassList} from './list';
import {Value} from './index';
export class SassMap extends Value {
constructor(contents: OrderedMap<Value, Value>);
static empty(): SassMap;
constructor(contents?: OrderedMap<Value, Value>);
get contents(): OrderedMap<Value, Value>;
tryMap(): OrderedMap<Value, Value>;
get(key: Value): Value | undefined;
get(index: number): SassList | undefined;
tryMap(): SassMap;
}

View File

@ -3,15 +3,15 @@ import {List} from 'immutable';
import {Value} from './index';
export class SassNumber extends Value {
constructor(value: number, unit?: string);
static withUnits(
constructor(
value: number,
options?: {
numeratorUnits?: string[] | List<string>;
denominatorUnits?: string[] | List<string>;
}
): SassNumber;
unit?:
| string
| {
numeratorUnits?: string[] | List<string>;
denominatorUnits?: string[] | List<string>;
}
);
get value(): number;

View File

@ -9,7 +9,7 @@ export class SassString extends Value {
}
);
static empty(options?: {/** @default true */ quotes?: boolean}): SassString;
constructor(options?: {quotes?: boolean});
get text(): string;

View File

@ -48,8 +48,19 @@ export interface Options<sync extends 'sync' | 'async'> {
/**
* When the compiler encounters a global function call with a signature that
* does not match that of a built-in function, but matches a key in this map,
* it must call the associated `CustomFunction` and return its result.
* does not match that of a built-in function, but matches a key in this
* record, it must call the associated `CustomFunction` and return its result.
* If the function throws an error or returns anything other than a `Value`,
* the compiler should treat it as the Sass function throwing an error.
*
* > As in the rest of Sass, `_`s and `-`s are considered equivalent when
* > determining which function signatures match.
*
* Before beginning compilation, if any key in this record is not an
* [<ident-token>] followed immediately by an `ArgumentDeclaration`, the
* compiler must throw an error.
*
* [<ident-token>]: https://drafts.csswg.org/css-syntax-3/#ident-token-diagram
*
* The compiler must throw an error if the `CustomFunction` does not return a
* `Value`.

View File

@ -7,97 +7,79 @@ import {Value} from './index';
*/
export class SassColor extends Value {
/**
* Creates an RGB color:
* - If `options.red` is set:
*
* - Let `sassRed` be a Sass number with a value of `red` `fuzzyRound`ed to
* the nearest integer.
* - Let `red` be a Sass number with a value of `options.red` `fuzzyRound`ed
* to the nearest integer.
*
* - Let `sassGreen` be a Sass number with a value of `green` `fuzzyRound`ed
* to the nearest integer
* - Let `green` be a Sass number with a value of `options.green`
* `fuzzyRound`ed to the nearest integer.
*
* - Let `sassBlue` be a Sass number with a value of `blue` `fuzzyRound`ed to
* the nearest integer.
* - Let `blue` be a Sass number with a value of `options.blue`
* `fuzzyRound`ed to the nearest integer.
*
* - If `alpha` was passed, let `sassAlpha` be a Sass number with a value of
* `alpha`.
* - If `options.alpha` is set, let `alpha` be a Sass number with a value of
* `options.alpha`. Otherwise, let `alpha` be `null`.
*
* - Set `internal` to the result of running [`rgb()`] with the following
* inputs:
* - `$red` set to `sassRed`
* - `$green` set to `sassGreen`
* - `$blue` set to `sassBlue`
* - If `alpha` was passed, `$alpha` set to `sassAlpha`
* - Set `internal` to the result of running [`rgb()`] with `$red`, `$green`,
* `$blue`, and `$alpha`.
*
* [`rgb()`]: ../spec/functions.md#rgb-and-rgba
* [`rgb()`]: ../../functions.md#rgb-and-rgba
*
* - Otherwise, if `options.saturation` is set:
*
* - Let `hue` be a Sass number with a value of `options.hue`.
*
* - Let `saturation` be a Sass number with a value of `options.saturation`.
*
* - Let `lightness` be a Sass number with a value of `options.lightness`.
*
* - If `options.alpha` is set, let `alpha` be a Sass number with a value of
* `options.alpha`. Otherwise, let `alpha` be `null`.
*
* - Set `internal` to the result of running [`hsl()`] with `$hue`, `$saturation`,
* `$lightness`, and `$alpha`.
*
* [`hsl()`]: ../../functions.md#hsl-and-hsla
*
* - Otherwise, if `options.whiteness` is set:
*
* - Let `hue` be a Sass number with a value of `options.hue`.
*
* - Let `whiteness` be a Sass number with a value of `options.whiteness`.
*
* - Let `blackness` be a Sass number with a value of `options.blackness`.
*
* - If `options.alpha` is set, let `alpha` be a Sass number with a value of
* `options.alpha`. Otherwise, let `alpha` be `null`.
*
* - Set `internal` to the result of running [`hwb()`] with `$hue`, `$whiteness`,
* `$blackness`, and `$alpha`.
*
* [`hwb()`]: ../../built-in-modules/color.md#hwb
*
* - Return `this`.
*/
static rgb(
red: number,
green: number,
blue: number,
alpha?: number
): SassColor;
constructor(options: {
red: number;
green: number;
blue: number;
alpha?: number;
});
/**
* Creates an HSL color:
*
* - Let `sassHue` be a Sass number with a value of `hue`.
*
* - Let `sassSaturation` be a Sass number with a value of `saturation`.
*
* - Let `sassLightness` be a Sass number with a value of `lightness`.
*
* - If `alpha` was passed, let `sassAlpha` be a Sass number with a value of
* `alpha`.
*
* - Set `internal` to the result of running [`hsl()`] with the following
* inputs:
* - `$hue` set to `sassHue`
* - `$saturation` set to `sassSaturation`
* - `$lightness` set to `sassLightness`
* - If `alpha` was passed, `$alpha` set to `sassAlpha`
*
* [`hsl()`]: ../spec/functions.md#hsl-and-hsla
*
* - Return `this`.
*/
static hsl(
hue: number,
saturation: number,
lightness: number,
alpha?: number
): SassColor;
constructor(options: {
hue: number;
saturation: number;
lightness: number;
alpha?: number;
});
/**
* Creates an HWB color:
*
* - Let `sassHue` be a Sass number with a value of `hue`.
*
* - Let `sassWhiteness` be a Sass number with a value of `whiteness`.
*
* - Let `sassBlackness` be a Sass number with a value of `blackness`.
*
* - If `alpha` was passed, let `sassAlpha` be a Sass number with a value of
* `alpha`.
*
* - Set `internal` to the result of running [`hwb()`] with the following
* inputs:
* - `$hue` set to `sassHue`
* - `$whiteness` set to `sassWhiteness`
* - `$blackness` set to `sassBlackness`
* - If `alpha` was passed, `$alpha` set to `sassAlpha`
*
* [`hwb()`]: ../spec/color.md#hwb
*
* - Return `this`.
*/
static hwb(
hue: number,
whiteness: number,
blackness: number,
alpha?: number
): SassColor;
constructor(options: {
hue: number;
whiteness: number;
blackness: number;
alpha?: number;
});
/** `internal`'s red channel. */
get red(): number;
@ -154,89 +136,70 @@ export class SassColor extends Value {
* Returns a new copy of `this` with one or more changes made to the RGB
* channels:
*
* - Let `oldColor` be `this`.
* - If `options.whiteness` or `options.blackness` is set:
*
* - If `red` was passed, let `newRed = red`.
* - Otherwise, let `newRed = oldColor.red`.
* - Let `hue` be `options.hue` if it was passed, or `this.hue` otherwise.
*
* - If `green` was passed, let `newGreen = green`.
* - Otherwise, let `newGreen = oldColor.green`.
* - Let `whiteness` be `options.whiteness` if it was passed, or
* `this.whiteness` otherwise.
*
* - If `blue` was passed, let `newBlue = blue`.
* - Otherwise, let `newBlue = oldColor.blue`.
* - Let `blackness` be `options.blackness` if it was passed, or
* `this.blackness` otherwise.
*
* - If `alpha` was passed, let `newAlpha = alpha`.
* - Otherwise, let `newAlpha = oldColor.alpha`.
* - Let `alpha` be `options.alpha` if it was passed, or `this.alpha`
* otherwise.
*
* - Return the result of
* `SassColor.rgb(newRed, newGreen, newBlue, newAlpha)`.
* - Return the result of `SassColor({hue, whiteness, blackness, alpha})`.
*
* - Otherwise, if `options.hue`, `options.saturation`, or `options.lightness`
* is set:
*
* - Let `hue` be `options.hue` if it was passed, or `this.hue` otherwise.
*
* - Let `saturation` be `options.saturation` if it was passed, or
* `this.saturation` otherwise.
*
* - Let `lightness` be `options.lightness` if it was passed, or
* `this.lightness` otherwise.
*
* - Let `alpha` be `options.alpha` if it was passed, or `this.alpha`
* otherwise.
*
* - Return the result of `SassColor({hue, saturation, lightness, alpha})`.
*
* - Otherwise:
*
* - Let `red` be `options.red` if it was passed, or `this.red` otherwise.
*
* - Let `green` be `options.green` if it was passed, or `this.green`
* otherwise.
*
* - Let `blue` be `options.blue` if it was passed, or `this.blue`
* otherwise.
*
* - Let `alpha` be `options.alpha` if it was passed, or `this.alpha`
* otherwise.
*
* - Return the result of `SassColor({red, green, blue, alpha})`.
*/
changeRgb(options: {
change(options: {
red?: number;
green?: number;
blue?: number;
alpha?: number;
}): SassColor;
/**
* Returns a new copy of `this` with one or more changes made to the HSL
* values:
*
* - Let `oldColor` be `this`.
*
* - If `hue` was passed, let `newHue = hue`.
* - Otherwise, let `newHue = oldColor.hue`.
*
* - If `saturation` was passed, let `newSaturation = saturation`.
* - Otherwise, let `newSaturation = oldColor.saturation`.
*
* - If `lightness` was passed, let `newLightness = lightness`.
* - Otherwise, let `newLightness = oldColor.lightness`.
*
* - If `alpha` was passed, let `newAlpha = alpha`.
* - Otherwise, let `newAlpha = oldColor.alpha`.
*
* - Return the result of
* `SassColor.hsl(newHue, newSaturation, newLightness, newAlpha)`.
*/
changeHsl(options: {
change(options: {
hue?: number;
saturation?: number;
lightness?: number;
alpha?: number;
}): SassColor;
/**
* Returns a new copy of `this` with one or more changes made to the HWB
* values:
*
* - Let `oldColor` be `this`.
*
* - If `hue` was passed, let `newHue = hue`.
* - Otherwise, let `newHue = oldColor.hue`.
*
* - If `whiteness` was passed, let `newWhiteness = whiteness`.
* - Otherwise, let `newWhiteness = oldColor.whiteness`.
*
* - If `blackness` was passed, let `newBlackness = blackness`.
* - Otherwise, let `newBlackness = oldColor.blackness`.
*
* - If `alpha` was passed, let `newAlpha = alpha`.
* - Otherwise, let `newAlpha = oldColor.alpha`.
*
* - Return the result of
* `SassColor.hwb(newHue, newWhiteness, newBlackness, newAlpha)`.
*/
changeHwb(options: {
change(options: {
hue?: number;
whiteness?: number;
blackness?: number;
alpha?: number;
}): SassColor;
/**
* Returns a new copy of `this` with `internal`'s alpha channel set to
* `alpha`.
*/
changeAlpha(alpha: number): SassColor;
}

View File

@ -1,4 +1,4 @@
import {List, OrderedMap, ValueObject} from 'immutable';
import {List, ValueObject} from 'immutable';
import {SassBoolean} from './boolean';
import {SassColor} from './color';
@ -85,6 +85,15 @@ export abstract class Value implements ValueObject {
*/
sassIndexToListIndex(sassIndex: Value, name?: string): number;
/**
* - Return `this.asList.get(index)`.
*
* > Note that the `immutable` package uses zero-based indexing, with negative
* > numbers indexing backwards from the end of the list. Non-integer indices
* > are rounded down.
*/
get(index: number): Value | undefined;
/**
* Asserts that `this` is a `SassBoolean`:
*
@ -148,20 +157,16 @@ export abstract class Value implements ValueObject {
assertString(name?: string): SassString;
/**
* Returns `this`'s map contents, if it can be interpreted as a map.
* Returns `this` interpreted as a map.
*
* - If `internal` is a Sass map:
* - Let `result` be an empty `OrderedMap`.
* - Add each key and value from `internal`'s contents to `result`, in
* order.
* - Return `result`.
* - If `this` is a `SassMap`, return `this`.
*
* - Otherwise, if `internal` is an empty Sass list, return an empty
* `OrderedMap`.
* - Otherwise, if `internal` is an empty Sass list, return a `SassMap` with
* its `internal` set to an empty `OrderedMap`.
*
* - Otherwise, return `null`.
*/
tryMap(): OrderedMap<Value, Value> | null;
tryMap(): SassMap | null;
/** Whether `this == other` in SassScript. */
equals(other: Value): boolean;

View File

@ -18,33 +18,32 @@ export class SassList extends Value {
/**
* Creates a Sass list:
*
* - If the first argument is an `Array` or a `List`:
* - Let `contents` be the first argument.
* - Let `options` be the second argument, or `{}` if it's undefined.
*
* - Otherwise:
* - Let `contents` be `[]`.
* - Let `options` be the first argument, or `{}` if it's undefined.
*
* - Let `separator` be `options.separator`, or `','` if that's undefined.
*
* - Let `brackets` be `options.brackets`, or `false` if that's undefined.
*
* - Set `internal` to a Sass list with contents set to `contents`, separator
* set to `options.separator`, and brackets set to `options.brackets`.
* set to `separator`, and brackets set to `brackets`.
*
* - Return `this`.
*/
constructor(
contents: Value[] | List<Value>,
options?: {
/** @default ',' */
separator?: ListSeparator;
/** @default false */
brackets?: boolean;
}
);
/**
* Creates an empty Sass list:
*
* - Set `internal` to an empty Sass list with separator set to
* `options.separator` and brackets set to `options.brackets`.
* - Return `this`.
*/
static empty(options?: {
/** @default null */
separator?: ListSeparator;
/** @default false */
brackets?: boolean;
}): SassList;
constructor(options?: {separator?: ListSeparator; brackets?: boolean});
/** `internal`'s list separator. */
get separator(): ListSeparator;

View File

@ -1,5 +1,6 @@
import {OrderedMap} from 'immutable';
import {SassList} from './list';
import {Value} from './index';
/**
@ -11,18 +12,11 @@ export class SassMap extends Value {
/**
* Creates a Sass map:
*
* - If `contents` is undefined, set it to an empty `OrderedMap`.
* - Set `internal` to a Sass map with contents set to `contents`.
* - Return `this`.
*/
constructor(contents: OrderedMap<Value, Value>);
/**
* Creates an empty Sass map:
*
* - Set `internal` to an empty Sass map.
* - Return `this`.
*/
static empty(): SassMap;
constructor(contents?: OrderedMap<Value, Value>);
/**
* Returns a map containing `internal`'s contents:
@ -33,6 +27,15 @@ export class SassMap extends Value {
*/
get contents(): OrderedMap<Value, Value>;
/** Returns `this.contents`. */
tryMap(): OrderedMap<Value, Value>;
/**
* - If the first argument is a JavaScript number, pass it to
* `this.asList.get` and return the result.
*
* - Otherwise, pass it to `this.contents.get` and return the result.
*/
get(key: Value): Value | undefined;
get(index: number): SassList | undefined;
tryMap(): SassMap;
}

View File

@ -11,28 +11,34 @@ export class SassNumber extends Value {
/**
* Creates a Sass number:
*
* - Set `internal` to a Sass number with value set to `value` and a single
* numerator unit equal to `unit` (if passed).
* - Return `this`.
*/
constructor(value: number, unit?: string);
/**
* Creates a Sass number:
* - If the second argument is undefined:
*
* - Set `internal` to a Sass number with a value of `value`.
*
* - Otherwise, if the second argument is a string:
*
* - Set `internal` to a Sass number with a value of `value` and that string
* as its single numerator unit.
*
* - Otherwise,
*
* - Let `options` be the second argument.
*
* - Set `internal` to a Sass number with a value of `value`,
* `options.numeratorUnits` as its numerator units (if passed), and
* `options.denominatorUnits` as its denominator units (if passed).
*
* - Set `internal` to a Sass number with value set to `value`, numerator
* units set to `options.numeratorUnits` (if passed), and denominator units
* set to `options.denominatorUnits` (if passed).
* - Set `internal` to the result of `simplify`ing `internal`.
* - Return `this`.
*/
static withUnits(
constructor(
value: number,
options?: {
numeratorUnits?: string[] | List<string>;
denominatorUnits?: string[] | List<string>;
}
): SassNumber;
unit?:
| string
| {
numeratorUnits?: string[] | List<string>;
denominatorUnits?: string[] | List<string>;
}
);
/** `internal`'s value. */
get value(): number;
@ -112,7 +118,7 @@ export class SassNumber extends Value {
/**
* Whether `internal` is [compatible] with `unit`.
*
* [compatible]: ../spec/types/number.md#compatible-units
* [compatible]: ../../types/number.md#compatible-units
*/
compatibleWithUnit(unit: string): boolean;

View File

@ -9,18 +9,30 @@ export class SassString extends Value {
/**
* Creates a Sass string:
*
* - If the first argument is a string:
* - Let `text` be the first argument.
* - Let `options` be the second argument, or `{}` if it's undefined.
*
* - Otherwise:
* - Let `text` be `""`.
* - Let `options` be the first argument, or `{}` if it's undefined.
*
* - Let `quotes` be `options.quotes`, or `true` if that's undefined.
*
* - Set `internal` to a Sass string with contents set to `text` and quoted
* value set to `options.quotes`.
* set to `quotes`.
*
* - Return `this`.
*/
constructor(
text: string,
options?: {
/** @default true */
quotes?: boolean;
}
);
constructor(options?: {/** @default true */ quotes?: boolean});
/**
* Creates an empty Sass string:
*
@ -28,7 +40,6 @@ export class SassString extends Value {
* `options.quotes`.
* - Return `this`.
*/
static empty(options?: {/** @default true */ quotes?: boolean}): SassString;
/** The contents of `internal` serialized as UTF-16 code units. */
get text(): string;