Skip to content

Commit 573e2e3

Browse files
authored
Implement the host side of the deprecations API (#279)
1 parent 03934e2 commit 573e2e3

File tree

7 files changed

+225
-13
lines changed

7 files changed

+225
-13
lines changed

lib/index.mjs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ export const AsyncCompiler = sass.AsyncCompiler;
99
export const Compiler = sass.Compiler;
1010
export const initAsyncCompiler = sass.initAsyncCompiler;
1111
export const initCompiler = sass.initCompiler;
12+
export const deprecations = sass.deprecations;
13+
export const Version = sass.Version;
1214
export const Logger = sass.Logger;
1315
export const CalculationInterpolation = sass.CalculationInterpolation;
1416
export const CalculationOperation = sass.CalculationOperation;
@@ -86,6 +88,14 @@ export default {
8688
defaultExportDeprecation();
8789
return sass.Compiler;
8890
},
91+
get deprecations() {
92+
defaultExportDeprecation();
93+
return sass.deprecations;
94+
},
95+
get Version() {
96+
defaultExportDeprecation();
97+
return sass.Version;
98+
},
8999
get Logger() {
90100
defaultExportDeprecation();
91101
return sass.Logger;

lib/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ export {
3535
} from './src/compile';
3636
export {initAsyncCompiler, AsyncCompiler} from './src/compiler/async';
3737
export {initCompiler, Compiler} from './src/compiler/sync';
38+
export {
39+
deprecations,
40+
Deprecation,
41+
DeprecationOrId,
42+
DeprecationStatus,
43+
Version,
44+
} from './src/deprecations';
3845
export {render, renderSync} from './src/legacy';
3946

4047
export const info = `sass-embedded\t${pkg.version}`;

lib/src/compiler/utils.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import * as p from 'path';
66
import * as supportsColor from 'supports-color';
7+
import {deprecations, getDeprecationIds, Deprecation} from '../deprecations';
78
import {deprotofySourceSpan} from '../deprotofy-span';
89
import {Dispatcher, DispatcherHandlers} from '../dispatcher';
910
import {Exception} from '../exception';
@@ -75,6 +76,9 @@ function newCompileRequest(
7576
verbose: !!options?.verbose,
7677
charset: !!(options?.charset ?? true),
7778
silent: options?.logger === Logger.silent,
79+
fatalDeprecation: getDeprecationIds(options?.fatalDeprecations ?? []),
80+
silenceDeprecation: getDeprecationIds(options?.silenceDeprecations ?? []),
81+
futureDeprecation: getDeprecationIds(options?.futureDeprecations ?? []),
7882
});
7983

8084
switch (options?.style ?? 'expanded') {
@@ -137,6 +141,13 @@ export function newCompileStringRequest(
137141
return request;
138142
}
139143

144+
/** Type guard to check that `id` is a valid deprecation ID. */
145+
function validDeprecationId(
146+
id: string | number | symbol | undefined
147+
): id is keyof typeof deprecations {
148+
return !!id && id in deprecations;
149+
}
150+
140151
/** Handles a log event according to `options`. */
141152
export function handleLogEvent(
142153
options: OptionsWithLegacy<'sync' | 'async'> | undefined,
@@ -148,6 +159,9 @@ export function handleLogEvent(
148159
if (options?.legacy) message = removeLegacyImporter(message);
149160
let formatted = event.formatted;
150161
if (options?.legacy) formatted = removeLegacyImporter(formatted);
162+
const deprecationType = validDeprecationId(event.deprecationType)
163+
? deprecations[event.deprecationType]
164+
: null;
151165

152166
if (event.type === proto.LogEventType.DEBUG) {
153167
if (options?.logger?.debug) {
@@ -159,10 +173,18 @@ export function handleLogEvent(
159173
}
160174
} else {
161175
if (options?.logger?.warn) {
162-
const params: {deprecation: boolean; span?: SourceSpan; stack?: string} =
163-
{
164-
deprecation: event.type === proto.LogEventType.DEPRECATION_WARNING,
165-
};
176+
const params: (
177+
| {
178+
deprecation: true;
179+
deprecationType: Deprecation;
180+
}
181+
| {deprecation: false}
182+
) & {
183+
span?: SourceSpan;
184+
stack?: string;
185+
} = deprecationType
186+
? {deprecation: true, deprecationType: deprecationType}
187+
: {deprecation: false};
166188
if (span) params.span = span;
167189

168190
const stack = event.stackTrace;

lib/src/deprecations.ts

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// Copyright 2024 Google LLC. Use of this source code is governed by an
2+
// MIT-style license that can be found in the LICENSE file or at
3+
// https://opensource.org/licenses/MIT.
4+
5+
import * as api from './vendor/sass';
6+
7+
export {Deprecation, DeprecationOrId, DeprecationStatus} from './vendor/sass';
8+
9+
export class Version implements api.Version {
10+
constructor(
11+
readonly major: number,
12+
readonly minor: number,
13+
readonly patch: number
14+
) {}
15+
static parse(version: string): Version {
16+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)$/);
17+
if (match === null) {
18+
throw new Error(`Invalid version ${version}`);
19+
}
20+
return new Version(
21+
parseInt(match[1]),
22+
parseInt(match[2]),
23+
parseInt(match[3])
24+
);
25+
}
26+
}
27+
28+
/**
29+
* Returns whether the given deprecation was active in the given version.
30+
*/
31+
function isActiveIn(deprecation: api.Deprecation, version: Version) {
32+
const deprecatedIn = deprecation.deprecatedIn;
33+
if (deprecation.status !== 'active' || !deprecatedIn) return false;
34+
if (version.major > deprecatedIn.major) return true;
35+
if (version.major < deprecatedIn.major) return false;
36+
if (version.minor > deprecatedIn.minor) return true;
37+
if (version.minor < deprecatedIn.minor) return false;
38+
return version.patch >= deprecatedIn.patch;
39+
}
40+
41+
/**
42+
* Converts a mixed array of deprecations, IDs, and versions to an array of IDs
43+
* that's ready to include in a CompileRequest.
44+
*/
45+
export function getDeprecationIds(
46+
arr: (api.DeprecationOrId | Version)[]
47+
): string[] {
48+
return arr.flatMap(item => {
49+
if (item instanceof Version) {
50+
return Object.values(deprecations)
51+
.filter(deprecation => isActiveIn(deprecation, item))
52+
.map(deprecation => deprecation.id);
53+
} else if (typeof item === 'string') {
54+
return item;
55+
}
56+
return item.id;
57+
});
58+
}
59+
60+
export const deprecations: typeof api.deprecations = {
61+
'call-string': {
62+
id: 'call-string',
63+
status: 'active',
64+
deprecatedIn: new Version(0, 0, 0),
65+
obsoleteIn: null,
66+
description: 'Passing a string directly to meta.call().',
67+
},
68+
elseif: {
69+
id: 'elseif',
70+
status: 'active',
71+
deprecatedIn: new Version(1, 3, 2),
72+
obsoleteIn: null,
73+
description: '@elseif.',
74+
},
75+
'moz-document': {
76+
id: 'moz-document',
77+
status: 'active',
78+
deprecatedIn: new Version(1, 7, 2),
79+
obsoleteIn: null,
80+
description: '@-moz-document.',
81+
},
82+
'relative-canonical': {
83+
id: 'relative-canonical',
84+
status: 'active',
85+
deprecatedIn: new Version(1, 14, 2),
86+
obsoleteIn: null,
87+
},
88+
'new-global': {
89+
id: 'new-global',
90+
status: 'active',
91+
deprecatedIn: new Version(1, 17, 2),
92+
obsoleteIn: null,
93+
description: 'Declaring new variables with !global.',
94+
},
95+
'color-module-compat': {
96+
id: 'color-module-compat',
97+
status: 'active',
98+
deprecatedIn: new Version(1, 23, 0),
99+
obsoleteIn: null,
100+
description:
101+
'Using color module functions in place of plain CSS functions.',
102+
},
103+
'slash-div': {
104+
id: 'slash-div',
105+
status: 'active',
106+
deprecatedIn: new Version(1, 33, 0),
107+
obsoleteIn: null,
108+
description: '/ operator for division.',
109+
},
110+
'bogus-combinators': {
111+
id: 'bogus-combinators',
112+
status: 'active',
113+
deprecatedIn: new Version(1, 54, 0),
114+
obsoleteIn: null,
115+
description: 'Leading, trailing, and repeated combinators.',
116+
},
117+
'strict-unary': {
118+
id: 'strict-unary',
119+
status: 'active',
120+
deprecatedIn: new Version(1, 55, 0),
121+
obsoleteIn: null,
122+
description: 'Ambiguous + and - operators.',
123+
},
124+
'function-units': {
125+
id: 'function-units',
126+
status: 'active',
127+
deprecatedIn: new Version(1, 56, 0),
128+
obsoleteIn: null,
129+
description: 'Passing invalid units to built-in functions.',
130+
},
131+
'duplicate-var-flags': {
132+
id: 'duplicate-var-flags',
133+
status: 'active',
134+
deprecatedIn: new Version(1, 62, 0),
135+
obsoleteIn: null,
136+
description: 'Using !default or !global multiple times for one variable.',
137+
},
138+
'null-alpha': {
139+
id: 'null-alpha',
140+
status: 'active',
141+
deprecatedIn: new Version(1, 62, 3),
142+
obsoleteIn: null,
143+
description: 'Passing null as alpha in the JS API.',
144+
},
145+
'abs-percent': {
146+
id: 'abs-percent',
147+
status: 'active',
148+
deprecatedIn: new Version(1, 65, 0),
149+
obsoleteIn: null,
150+
description: 'Passing percentages to the Sass abs() function.',
151+
},
152+
'fs-importer-cwd': {
153+
id: 'fs-importer-cwd',
154+
status: 'active',
155+
deprecatedIn: new Version(1, 73, 0),
156+
obsoleteIn: null,
157+
description:
158+
'Using the current working directory as an implicit load path.',
159+
},
160+
import: {
161+
id: 'import',
162+
status: 'future',
163+
deprecatedIn: null,
164+
obsoleteIn: null,
165+
description: '@import rules.',
166+
},
167+
'user-authored': {
168+
id: 'user-authored',
169+
status: 'user',
170+
deprecatedIn: null,
171+
obsoleteIn: null,
172+
},
173+
};

lib/src/importer-registry.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ export class NodePackageImporter {
2121
entryPointDirectory = entryPointDirectory
2222
? p.resolve(entryPointDirectory)
2323
: require.main?.filename
24-
? p.dirname(require.main.filename)
25-
: // TODO: Find a way to use `import.meta.main` once
26-
// https://github.com/nodejs/node/issues/49440 is done.
27-
process.argv[1]
28-
? createRequire(process.argv[1]).resolve(process.argv[1])
29-
: undefined;
24+
? p.dirname(require.main.filename)
25+
: // TODO: Find a way to use `import.meta.main` once
26+
// https://github.com/nodejs/node/issues/49440 is done.
27+
process.argv[1]
28+
? createRequire(process.argv[1]).resolve(process.argv[1])
29+
: undefined;
3030
if (!entryPointDirectory) {
3131
throw new Error(
3232
'The Node package importer cannot determine an entry point ' +

lib/src/legacy/importer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,8 @@ export class LegacyImporterWrapper<sync extends 'sync' | 'async'>
216216
const syntax = canonicalUrl.pathname.endsWith('.sass')
217217
? 'indented'
218218
: canonicalUrl.pathname.endsWith('.css')
219-
? 'css'
220-
: 'scss';
219+
? 'css'
220+
: 'scss';
221221

222222
let contents =
223223
this.lastContents ??

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "sass-embedded",
33
"version": "1.72.0",
4-
"protocol-version": "2.5.0",
4+
"protocol-version": "2.6.0",
55
"compiler-version": "1.72.0",
66
"description": "Node.js library that communicates with Embedded Dart Sass using the Embedded Sass protocol",
77
"repository": "sass/embedded-host-node",

0 commit comments

Comments
 (0)