sass/accepted/deprecations-api.md

17 KiB

Deprecations API: Draft 3.1

(Issue, Changelog)

Table of Contents

Background

This section is non-normative.

We recently added support to Dart Sass that allowed users to opt in to treating deprecation warnings as errors (on a per-deprecation basis), as well as opting in early to certain future deprecations. This is currently supported on the command line and via the Dart API, but we'd like to extend this support to the JS API as well.

We would also like to add support for silencing a particular deprecation's warnings, primarily to enable a gentler process for deprecating @import.

Summary

This section is non-normative.

This proposal adds a new Deprecation interface and Version class to the JS API, three new optional properties on Options (fatalDeprecations, silenceDeprecations, and futureDeprecations), a new parameter on Logger.warn (options.deprecationType) two type aliases (DeprecationOrId and DeprecationStatus) and a new object deprecations that contains the various Deprecation objects.

All deprecations are specified in deprecations, and any new deprecations added in the future (even those specific to a particular implementation) should update the specification accordingly. Deprecations should never be removed from the specification; when the behavior being deprecated is removed (i.e. there's a major version release), the deprecation status should be changed to obsolete, but remain in the specification.

Every Deprecation has a unique id, one of four status values, and (optionally) a human-readable description. Depending on the status, each deprecation may also have a deprecatedIn version and an obsoleteIn version that specify the compiler versions the deprecation became active and became obsolete in, respectively.

Design Decisions

Exposing the Full Deprecation Interface

One alternative to specifying a full Deprecation interface is to just have the relevant APIs take in string IDs. We considered this, but concluded that each deprecation has additional metadata that users of the API may wish to access (for example, a bundler may wish to surface the description and deprecatedIn version to its users).

Formally Specifying the Deprecations

We chose to make the list of deprecations part of the specification itself, as this ensures that the language-wide deprecations are consistent across implementations. However, if an implementation wishes to add a deprecation that applies only to itself, it may still do so.

Additionally, while a deprecation's status is part of the specification, we chose to leave the deprecatedIn and obsoleteIn versions of each deprecation out of the specification. As the two current implementers of this API are both based on Dart Sass, these versions are currently consistent across implementations in practice, potential future implementers should not need to be tied to Dart Sass's versioning.

Warnings for Invalid Deprecations and Precedence of Options

Whenever potentially invalid sets of deprecations are passed to any of the options, we choose to emit warnings rather than errors, as the status of each deprecation can change over time, and users may share a configuration when compiling across multiple implementations/versions whose dependency statuses may not be in sync.

The situations we chose to warn for are:

  • an invalid string ID.

    This is disallowed by the API's types, but may still occur at runtime, and should be warned for accordingly.

  • a future deprecation is passed to fatalDeprecations but not futureDeprecations.

    In this scenario, the future deprecation will still be treated as fatal, 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.

    If a deprecation is obsolete, that means the breaking change has already happened, so making it fatal is a no-op.

  • passing anything other than an active deprecation to silenceDeprecations.

    This is particularly important for obsolete deprecations, since otherwise users may not be aware of a subtle breaking change for which they were previously silencing warnings. We also warn for passing Deprecation.userAuthored, since there's no way to distinguish between different deprecations from user-authored code, so silencing them as a group is inadvisable. Passing a future deprecation here is either a no-op, or cancels out passing it to futureDeprecations, so we warn for that as well.

  • passing a non-future deprecation to futureDeprecations.

    This is a no-op, so we should warn users so they can clean up their configuration.

API

import {SourceSpan} from '../spec/js-api';

Types

Options

declare module '../spec/js-api' {
  interface Options<sync extends 'sync' | 'async'> {

fatalDeprecations

A set of deprecations to treat as fatal.

If a deprecation warning of any provided type is encountered during compilation, the compiler must error instead.

The compiler should convert any string passed here to a Deprecation by indexing deprecations. If an invalid deprecation ID is passed here, the compiler must emit a warning. If a version is passed here, it should be treated equivalently to passing all active deprecations whose deprecatedIn version is less than or equal to it.

The compiler must emit a warning if a future deprecation that's not also included in futureDeprecations or any obsolete deprecation is included here.

If a deprecation is passed both here and to silenceDeprecations, a warning must be emitted, but making the deprecation fatal must take precedence.

fatalDeprecations?: (DeprecationOrId | Version)[];

silenceDeprecations

A set of active deprecations to ignore.

If a deprecation warning of any provided type is encountered during compilation, the compiler must ignore it.

The compiler should convert any string passed here to a Deprecation by indexing Deprecations. If an invalid deprecation ID is passed here, the compiler must emit a warning.

The compiler must emit a warning if any non-active deprecation is included here. If a future deprecation is included both here and in futureDeprecations, then silencing it takes precedence.

silenceDeprecations?: DeprecationOrId[];

futureDeprecations

A set of future deprecations to opt into early.

For each future deprecation provided here, the compiler must treat that deprecation as if it is active, emitting warnings as necessary (subject to fatalDeprecations and silenceDeprecations).

The compiler should convert any string passed here to a Deprecation by indexing Deprecations. If an invalid deprecation ID is passed here, the compiler must emit a warning.

The compiler must emit a warning if a non-future deprecation is included here.

futureDeprecations?: DeprecationOrId[];
} // Options

Logger

interface Logger {

warn

Update the third sub-bullet of bullet two to read:

If this warning is caused by behavior that used to be allowed but will be disallowed in the future, set options.deprecation to true and set options.deprecationType to the relevant Deprecation. Otherwise, set options.deprecation to false and leave options.deprecationType undefined.

warn?(
  message: string,
  options: {
    deprecation: boolean;
    deprecationType?: Deprecation;
    span?: SourceSpan;
    stack?: string;
  }
): void;
  } // Logger
} // module

Deprecations

interface Deprecations {

call-string

Deprecation for passing a string to call instead of get-function.

'call-string': Deprecation<'call-string'>;

elseif

Deprecation for @elseif.

elseif: Deprecation<'elseif'>;

moz-document

Deprecation for parsing @-moz-document.

'moz-document': Deprecation<'moz-document'>;

relative-canonical

Deprecation for importers using relative canonical URLs.

'relative-canonical': Deprecation<'relative-canonical'>;

new-global

Deprecation for declaring new variables with !global.

'new-global': Deprecation<'new-global'>;

color-module-compat

Deprecation for certain functions in the color module matching the behavior of their global counterparts for compatibility reasons.

'color-module-compat': Deprecation<'color-module-compat'>;

slash-div

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'.

'slash-div': Deprecation<'slash-div'>;

bogus-combinators

Deprecation for leading, trailing, and repeated combinators.

Update the proposal for bogus combinators to say that it emits deprecation warnings with ID 'bogus-combinators'.

'bogus-combinators': Deprecation<'bogus-combinators'>;

strict-unary

Deprecation for ambiguous + and - operators.

Update the proposal for strict unary operators to say that it emits deprecation warnings with ID 'strict-unary'.

'strict-unary': Deprecation<'strict-unary'>;

function-units

Deprecation for passing invalid units to certain built-in functions.

Update the proposals for function units, random with units, and angle units to say that they emit deprecation warnings with ID 'function-units'.

'function-units': Deprecation<'function-units'>;

duplicate-var-flags

Deprecation for using multiple !global or !default flags on a single variable.

This deprecation was never explicitly listed in a proposal.

'duplicate-var-flags': Deprecation<'duplicate-var-flags'>;

import

Deprecation for @import rules.

Update the proposal for the module system to say that, when @import is deprecated, Sass will emit deprecation warnings with ID 'import' when @import rules are encountered.

import: Deprecation<'import'>;

user-authored

Used for deprecations coming from user-authored code.

'user-authored': Deprecation<'user-authored', 'user'>;
} // Deprecations

DeprecationOrId

A deprecation, or the ID of one.

export type DeprecationOrId = Deprecation | keyof Deprecations;

DeprecationStatus

A deprecation's status.

export type DeprecationStatus = 'active' | 'user' | 'future' | 'obsolete';

Deprecation

A deprecated feature in the language.

export interface Deprecation<
  id extends keyof Deprecations = keyof Deprecations,
  status extends DeprecationStatus = DeprecationStatus
> {

id

A kebab-case ID for this deprecation.

id: id;

The status of this deprecation.

  • 'active' means this deprecation is currently enabled. deprecatedIn is non-null and obsoleteIn is null.
  • 'user' means this deprecation is from user-authored code. Both deprecatedIn and obsoleteIn are null.
  • 'future' means this deprecation is not yet enabled. Both deprecatedIn and obsoleteIn are null.
  • 'obsolete' means this deprecation is now obsolete, as the feature it was for has been fully removed. Both deprecatedIn and obsoleteIn are non-null.
status: status;

description

A brief user-readable description of this deprecation.

description?: string;

deprecatedIn

The compiler version this feature was first deprecated in.

This is implementation-dependent, so versions are not guaranteed to be consistent between different compilers. For future deprecations, or those originating from user-authored code, this is null.

deprecatedIn: status extends 'future' | 'user' ? null : Version;

obsoleteIn

The compiler version this feature was fully removed in, making the deprecation obsolete.

This is null for active and future deprecations.

obsoleteIn: status extends 'obsolete' ? Version : null;
} // Deprecation

Version

A semantic version of the compiler.

export class Version {

Constructor

Creates a new Version with its major, minor, and patch fields set to the corresponding arguments.

constructor(major: number, minor: number, patch: number);

major

The major version.

This must be a non-negative integer.

readonly major: number;

minor

The minor version.

This must be a non-negative integer.

readonly minor: number;

patch

The patch version.

This must be a non-negative integer.

readonly patch: number;

parse

Parses a string in the form "major.minor.patch" into a Version.

static parse(version: string): Version;
} // Version

Top-Level Members

declare module '../spec/js-api' {

deprecations

An object containing all of the deprecations.

export const deprecations: Deprecations;
} // module

Embedded Protocol

CompileRequest

fatal_deprecation

A set of deprecation IDs to treat as fatal.

If a deprecation warning of any provided type is encountered during compilation, the compiler must respond with a CompileFailure instead of a CompileSuccess.

The compiler must emit an event of type LogEventType.WARNING if any of the following is true:

  • an invalid deprecation ID is passed
  • an obsolete deprecation ID is passed
  • a future deprecation ID is passed that is not also passed to future_deprecation
  • a deprecation ID is passed both here and to silence_deprecation (making it fatal takes precedence)
repeated string fatal_deprecation = 15;

silence_deprecation

A set of deprecation IDs to ignore.

If a deprecation warning of any provided type is encountered during compilation, the compiler must ignore it.

The compiler must emit an event of type LogEventType.WARNING if an invalid deprecation ID or any non-active deprecation ID is passed here.

If a future deprecation ID is passed both here and to future_deprecation, then silencing it takes precedence.

repeated string silence_deprecation = 16;

future_deprecation

A set of future deprecations IDs to opt into early.

For each future deprecation ID provided here, the compiler must treat that deprecation as if it is active, emitting warnings as necessary (subject to fatal_deprecation and silence_deprecation).

The compiler must emit an event of type LogEventType.WARNING if an invalid deprecation ID or any non-future deprecation ID is passed here.

repeated string future_deprecation = 17;

LogEvent

deprecation_type

The deprecation ID for this warning, if type is LogEventType.DEPRECATION_WARNING.

optional string deprecation_type = 7;