Skip to content

Commit

Permalink
[Containing URL] Update the specification
Browse files Browse the repository at this point in the history
See #3247
  • Loading branch information
nex3 committed Sep 15, 2023
1 parent e1442fc commit 01a894f
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 44 deletions.
2 changes: 1 addition & 1 deletion accepted/prev-url.d.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ interface FileImporter<sync extends 'sync' | 'async' = 'sync' | 'async'> {
```ts
findFileUrl(
url: string,
options: {fromImport: boolean, containingUrl?: URL}
options: {fromImport: boolean; containingUrl?: URL}
): PromiseOr<URL | null, sync>;
```
Expand Down
62 changes: 48 additions & 14 deletions js-api-doc/importer.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,38 @@
import {Syntax} from './options';
import {PromiseOr} from './util/promise_or';

/**
* Contextual information passed to {@link Importer.canonicalize} and {@link
* FileImporter.findFileUrl}. Not all importers will need this information to
* resolve loads, but some may find it useful.
*/
export interface CanonicalizeContext {
/**
* Whether this is being invoked because of a Sass
* `@import` rule, as opposed to a `@use` or `@forward` rule.
*
* This should *only* be used for determining whether or not to load
* [import-only files](https://sass-lang.com/documentation/at-rules/import#import-only-files).
*/
fromImport: boolean;

/**
* The canonical URL of the file that contains the load, if that information
* is available.
*
* For an {@link Importer}, this is only passed when the `url` parameter is a
* relative URL _or_ when its [URL scheme] is included in {@link
* Importer.nonCanonicalSchemes}. This ensures that canonical URLs are always
* resolved the same way regardless of context.
*
* [URL scheme]: https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Web_mechanics/What_is_a_URL#scheme
*
* For a {@link FileImporter}, this is always available as long as Sass knows
* the canonical URL of the containing file.
*/
containingUrl: URL | null;
}

/**
* A special type of importer that redirects all loads to existing files on
* disk. Although this is less powerful than a full {@link Importer}, it
Expand Down Expand Up @@ -56,12 +88,6 @@ export interface FileImporter<
* @param url - The loaded URL. Since this might be relative, it's represented
* as a string rather than a {@link URL} object.
*
* @param options.fromImport - Whether this is being invoked because of a Sass
* `@import` rule, as opposed to a `@use` or `@forward` rule.
*
* This should *only* be used for determining whether or not to load
* [import-only files](https://sass-lang.com/documentation/at-rules/import#import-only-files).
*
* @returns An absolute `file:` URL if this importer recognizes the `url`.
* This may be only partially resolved: the compiler will automatically look
* for [partials](https://sass-lang.com/documentation/at-rules/use#partials),
Expand All @@ -85,7 +111,7 @@ export interface FileImporter<
*/
findFileUrl(
url: string,
options: {fromImport: boolean}
context: CanonicalizeContext
): PromiseOr<URL | null, sync>;

/** @hidden */
Expand Down Expand Up @@ -220,12 +246,6 @@ export interface Importer<sync extends 'sync' | 'async' = 'sync' | 'async'> {
* @param url - The loaded URL. Since this might be relative, it's represented
* as a string rather than a {@link URL} object.
*
* @param options.fromImport - Whether this is being invoked because of a Sass
* `@import` rule, as opposed to a `@use` or `@forward` rule.
*
* This should *only* be used for determining whether or not to load
* [import-only files](https://sass-lang.com/documentation/at-rules/import#import-only-files).
*
* @returns An absolute URL if this importer recognizes the `url`, or `null`
* if it doesn't. If this returns `null`, other importers or {@link
* Options.loadPaths | load paths} may handle the load.
Expand All @@ -242,7 +262,7 @@ export interface Importer<sync extends 'sync' | 'async' = 'sync' | 'async'> {
*/
canonicalize(
url: string,
options: {fromImport: boolean}
context: CanonicalizeContext
): PromiseOr<URL | null, sync>;

/**
Expand Down Expand Up @@ -272,6 +292,20 @@ export interface Importer<sync extends 'sync' | 'async' = 'sync' | 'async'> {

/** @hidden */
findFileUrl?: never;

/**
* A URL scheme or set of schemes (without the `:`) that this importer
* promises never to use for URLs returned by {@link canonicalize}. If it does
* return a URL with one of these schemes, that's an error.
*
* If this is set, any call to canonicalize for a URL with a non-canonical
* scheme will be passed {@link CanonicalizeContext.containingUrl} if it's
* known.
*
* These schemes may only contain lowercase ASCII letters, ASCII numerals,
* `+`, `-`, and `.`. They may not be empty.
*/
nonCanonicalScheme?: string | string[];
}

/**
Expand Down
7 changes: 6 additions & 1 deletion js-api-doc/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ export {
compileStringAsync,
} from './compile';
export {Exception} from './exception';
export {FileImporter, Importer, ImporterResult} from './importer';
export {
CanonicalizeContext,
FileImporter,
Importer,
ImporterResult,
} from './importer';
export {Logger, SourceSpan, SourceLocation} from './logger';
export {
CustomFunction,
Expand Down
69 changes: 51 additions & 18 deletions spec/embedded_sass.proto
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ message InboundMessage {
// registered for this compilation.
uint32 file_importer_id = 3;
}

// The set of URL schemes that are considered *non-canonical* for this
// importer. This must be empty unless `importer.importer_id` is set.
//
// If any element of this contains a character other than a lowercase
// ASCII letter, an ASCII numeral, U+002B (`+`), U+002D (`-`), or U+002E
// (`.`), the compiler must treat the compilation as failed.
repeated string non_canonical_scheme = 4;
}

// Importers (including load paths on the filesystem) to use when resolving
Expand Down Expand Up @@ -147,7 +155,9 @@ message InboundMessage {
// The successfully canonicalized URL.
//
// If this is not an absolute URL (including scheme), the compiler must
// treat that as an error thrown by the importer.
// treat that as an error thrown by the importer. If this URL's scheme is
// an `Importer.non_canonical_scheme` for the importer being invoked, the
// compiler must treat that as an error thrown by the importer.
string url = 2;

// An error message explaining why canonicalization failed.
Expand Down Expand Up @@ -442,15 +452,27 @@ message OutboundMessage {
// That is the result of the import.
string url = 4;

/// Whether this request comes from an `@import` rule.
///
/// When evaluating `@import` rules, URLs should canonicalize to an
/// [import-only file] if one exists for the URL being canonicalized.
/// Otherwise, canonicalization should be identical for `@import` and `@use`
/// rules.
///
/// [import-only file]: https://sass-lang.com/documentation/at-rules/import#import-only-files
// Whether this request comes from an `@import` rule.
//
// When evaluating `@import` rules, URLs should canonicalize to an
// [import-only file] if one exists for the URL being canonicalized.
// Otherwise, canonicalization should be identical for `@import` and `@use`
// rules.
//
// [import-only file]: https://sass-lang.com/documentation/at-rules/import#import-only-files
bool from_import = 5;

// The canonical URL of the [current source file] that contained the load
// to be canonicalized.
//
// [current source file]: ../spec.md#current-source-file
//
// The compiler must set this if and only if `url` is relative or its
// scheme is an `Importer.non_canonical_scheme` for the importer being
// invoked, unless the current source file has no canonical URL.
//
// [non-canonical-proto]: #non_canonical_scheme
optional string containing_url = 6;
}

// A request for a custom importer to load the contents of a stylesheet.
Expand Down Expand Up @@ -486,8 +508,13 @@ message OutboundMessage {
// * Let `fromImport` be `true` if the importer is being run for an
// `@import` and `false` otherwise.
//
// * Let `containingUrl` be the canonical URL of the [current source file]
// if it has one, or undefined otherwise.
//
//
// * Let `response` be the result of sending a `FileImportRequest` with
// `string` as its `url` and `fromImport` as `from_import`.
// `string` as its `url`, `fromImport` as `from_import`, and
// `containingUrl` as `containing_url`.
//
// * If `response.result` is null, return null.
//
Expand All @@ -511,6 +538,7 @@ message OutboundMessage {
//
// * Return `text`, `syntax`, and `resolved`.
//
// [current source file]: ../spec.md#current-source-file
// [resolving `url`]: https://github.com/sass/sass/tree/main/spec/modules.md#resolving-a-file-url
message FileImportRequest {
reserved 2;
Expand All @@ -525,15 +553,20 @@ message OutboundMessage {
// The (non-canonicalized) URL of the import.
string url = 4;

/// Whether this request comes from an `@import` rule.
///
/// When evaluating `@import` rules, filesystem importers should load an
/// [import-only file] if one exists for the URL being canonicalized.
/// Otherwise, canonicalization should be identical for `@import` and `@use`
/// rules.
///
/// [import-only file]: https://sass-lang.com/documentation/at-rules/import#import-only-files
// Whether this request comes from an `@import` rule.
//
// When evaluating `@import` rules, filesystem importers should load an
// [import-only file] if one exists for the URL being canonicalized.
// Otherwise, canonicalization should be identical for `@import` and `@use`
// rules.
//
// [import-only file]: https://sass-lang.com/documentation/at-rules/import#import-only-files
bool from_import = 5;

// The canonical URL of the [current source file] that contained the load
// being resolved. The compiler must set this unless the current source file
// has no canonical URL.
optional string containing_url = 6;
}

// A request to invoke a custom Sass function and return its result.
Expand Down
66 changes: 57 additions & 9 deletions spec/js-api/importer.d.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,27 @@ import {PromiseOr} from './util/promise_or';
## Table of Contents

* [Types](#types)
* [`CanonicalizeContext`](#canonicalizecontext)
* [`FileImporter`](#fileimporter)
* [`Importer`](#importer)
* [`nonCanonicalScheme`](#noncanonicalscheme)
* [`ImporterResult`](#importerresult)

## Types

### `CanonicalizeContext`

This is a data object passed into calls to `Importer.canonicalize()` and
`FileImporter.findFileUrl()`. Its fields are set as part of the function
invocations.

```ts
export interface CanonicalizeContext {
fromImport: boolean;
containingUrl: URL | null;
}
```

### `FileImporter`

This interface represents an [importer]. When the importer is invoked with a
Expand All @@ -33,14 +48,19 @@ string `string`:
* Let `fromImport` be `true` if the importer is being run for an `@import` and
`false` otherwise.

* Let `url` be the result of calling `findFileUrl` with `string` and
`fromImport`. If it returns a promise, wait for it to complete and use its
value instead, or rethrow its error if it rejects.
* Let `containingUrl` be the canonical URL of the [current source file] if it
has one, or undefined otherwise.

* Let `url` be the result of calling `findFileUrl` with `string`, `fromImport`,
and `containingUrl`. If it returns a promise, wait for it to complete and use
its value instead, or rethrow its error if it rejects.

* If `url` is null, return null.

* If `url`'s scheme is not `file`, throw an error.

[current source file]: ../spec.md#current-source-file

* Let `resolved` be the result of [resolving `url`].

[resolving `url`]: ../modules.md#resolving-a-file-url
Expand All @@ -65,7 +85,7 @@ export interface FileImporter<
> {
findFileUrl(
url: string,
options: {fromImport: boolean}
context: CanonicalizeContext
): PromiseOr<URL | null, sync>;

canonicalize?: never;
Expand All @@ -80,9 +100,18 @@ string `string`:
* Let `fromImport` be `true` if the importer is being run for an `@import` and
`false` otherwise.

* Let `url` be the result of calling `canonicalize` with `url` and `fromImport`.
If it returns a promise, wait for it to complete and use its value instead, or
rethrow its error if it rejects.
* If `string` is a relative URL, or if it's an absolute URL whose scheme is
non-canonical for this importer, let `containingUrl` be the canonical URL of
the [current source file]. Otherwise, or if the current source file has no
canonical URL, let `containingUrl` be undefined.

* Let `url` be the result of calling `canonicalize` with `string`, `fromImport`,
and `containingUrl`. If it returns a promise, wait for it to complete and use
its value instead, or rethrow its error if it rejects.

* If the scheme of `url` is [non-canonical] for this importer, throw an error.

[non-canonical]: #noncanonicalscheme

* If `url` is null, return null.

Expand All @@ -104,13 +133,32 @@ string `string`:
export interface Importer<sync extends 'sync' | 'async' = 'sync' | 'async'> {
canonicalize(
url: string,
options: {fromImport: boolean}
context: CanonicalizeContext
): PromiseOr<URL | null, sync>;

load(canonicalUrl: URL): PromiseOr<ImporterResult | null, sync>;

findFileUrl?: never;
}
```
#### `nonCanonicalScheme`
The set of URL schemes that are considered *non-canonical* for this importer. If
this is a single string, treat it as a list containing that string.
Before beginning compilation, throw an error if any element of this is empty or
contains a character other than a lowercase ASCII letter, an ASCII numeral,
U+002B (`+`), U+002D (`-`), or U+002E (`.`).
> Uppercase letters are normalized to lowercase in the `URL` constructor, so for
> simplicity and efficiency we only allow lowercase here.
```ts
nonCanonicalScheme?: string | string[];
```
```ts
} // Importer
```

### `ImporterResult`
Expand Down
7 changes: 6 additions & 1 deletion spec/js-api/index.d.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,12 @@ export {
compileStringAsync,
} from './compile';
export {Exception} from './exception';
export {FileImporter, Importer, ImporterResult} from './importer';
export {
CanonicalizeContext,
FileImporter,
Importer,
ImporterResult,
} from './importer';
export {Logger, SourceSpan, SourceLocation} from './logger';
export {
CustomFunction,
Expand Down

0 comments on commit 01a894f

Please sign in to comment.