Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Emit deprecation warnings for legacy JS API #331

Merged
merged 4 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 38 additions & 31 deletions lib/src/compiler/async.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
newCompileStringRequest,
} from './utils';
import {compilerCommand} from '../compiler-path';
import {activeDeprecationOptions} from '../deprecations';
import {FunctionRegistry} from '../function-registry';
import {ImporterRegistry} from '../importer-registry';
import {MessageTransformer} from '../message-transformer';
Expand Down Expand Up @@ -102,38 +103,44 @@ export class AsyncCompiler {
importers: ImporterRegistry<'async'>,
options?: OptionsWithLegacy<'async'> & {legacy?: boolean}
): Promise<CompileResult> {
const functions = new FunctionRegistry(options?.functions);

const dispatcher = createDispatcher<'async'>(
this.compilationId++,
this.messageTransformer,
{
handleImportRequest: request => importers.import(request),
handleFileImportRequest: request => importers.fileImport(request),
handleCanonicalizeRequest: request => importers.canonicalize(request),
handleFunctionCallRequest: request => functions.call(request),
}
);
dispatcher.logEvents$.subscribe(event => handleLogEvent(options, event));

const compilation = new Promise<proto.OutboundMessage_CompileResponse>(
(resolve, reject) =>
dispatcher.sendCompileRequest(request, (err, response) => {
this.compilations.delete(compilation);
// Reset the compilation ID when the compiler goes idle (no active
// compilations) to avoid overflowing it.
// https://github.com/sass/embedded-host-node/pull/261#discussion_r1429266794
if (this.compilations.size === 0) this.compilationId = 1;
if (err) {
reject(err);
} else {
resolve(response!);
}
})
);
this.compilations.add(compilation);
const optionsKey = Symbol();
activeDeprecationOptions.set(optionsKey, options ?? {});
try {
const functions = new FunctionRegistry(options?.functions);

const dispatcher = createDispatcher<'async'>(
this.compilationId++,
this.messageTransformer,
{
handleImportRequest: request => importers.import(request),
handleFileImportRequest: request => importers.fileImport(request),
handleCanonicalizeRequest: request => importers.canonicalize(request),
handleFunctionCallRequest: request => functions.call(request),
}
);
dispatcher.logEvents$.subscribe(event => handleLogEvent(options, event));

const compilation = new Promise<proto.OutboundMessage_CompileResponse>(
(resolve, reject) =>
dispatcher.sendCompileRequest(request, (err, response) => {
this.compilations.delete(compilation);
// Reset the compilation ID when the compiler goes idle (no active
// compilations) to avoid overflowing it.
// https://github.com/sass/embedded-host-node/pull/261#discussion_r1429266794
if (this.compilations.size === 0) this.compilationId = 1;
if (err) {
reject(err);
} else {
resolve(response!);
}
})
);
this.compilations.add(compilation);

return handleCompileResponse(await compilation);
return handleCompileResponse(await compilation);
} finally {
activeDeprecationOptions.delete(optionsKey);
}
}

/** Initialize resources shared across compilations. */
Expand Down
81 changes: 44 additions & 37 deletions lib/src/compiler/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
newCompileStringRequest,
} from './utils';
import {compilerCommand} from '../compiler-path';
import {activeDeprecationOptions} from '../deprecations';
import {Dispatcher} from '../dispatcher';
import {FunctionRegistry} from '../function-registry';
import {ImporterRegistry} from '../importer-registry';
Expand Down Expand Up @@ -108,44 +109,50 @@ export class Compiler {
importers: ImporterRegistry<'sync'>,
options?: OptionsWithLegacy<'sync'>
): CompileResult {
const functions = new FunctionRegistry(options?.functions);

const dispatcher = createDispatcher<'sync'>(
this.compilationId++,
this.messageTransformer,
{
handleImportRequest: request => importers.import(request),
handleFileImportRequest: request => importers.fileImport(request),
handleCanonicalizeRequest: request => importers.canonicalize(request),
handleFunctionCallRequest: request => functions.call(request),
}
);
this.dispatchers.add(dispatcher);

dispatcher.logEvents$.subscribe(event => handleLogEvent(options, event));

let error: unknown;
let response: proto.OutboundMessage_CompileResponse | undefined;
dispatcher.sendCompileRequest(request, (error_, response_) => {
this.dispatchers.delete(dispatcher);
// Reset the compilation ID when the compiler goes idle (no active
// dispatchers) to avoid overflowing it.
// https://github.com/sass/embedded-host-node/pull/261#discussion_r1429266794
if (this.dispatchers.size === 0) this.compilationId = 1;
if (error_) {
error = error_;
} else {
response = response_;
}
});

for (;;) {
if (!this.yield()) {
throw utils.compilerError('Embedded compiler exited unexpectedly.');
const optionsKey = Symbol();
activeDeprecationOptions.set(optionsKey, options ?? {});
try {
const functions = new FunctionRegistry(options?.functions);

const dispatcher = createDispatcher<'sync'>(
this.compilationId++,
this.messageTransformer,
{
handleImportRequest: request => importers.import(request),
handleFileImportRequest: request => importers.fileImport(request),
handleCanonicalizeRequest: request => importers.canonicalize(request),
handleFunctionCallRequest: request => functions.call(request),
}
);
this.dispatchers.add(dispatcher);

dispatcher.logEvents$.subscribe(event => handleLogEvent(options, event));

let error: unknown;
let response: proto.OutboundMessage_CompileResponse | undefined;
dispatcher.sendCompileRequest(request, (error_, response_) => {
this.dispatchers.delete(dispatcher);
// Reset the compilation ID when the compiler goes idle (no active
// dispatchers) to avoid overflowing it.
// https://github.com/sass/embedded-host-node/pull/261#discussion_r1429266794
if (this.dispatchers.size === 0) this.compilationId = 1;
if (error_) {
error = error_;
} else {
response = response_;
}
});

for (;;) {
if (!this.yield()) {
throw utils.compilerError('Embedded compiler exited unexpectedly.');
}

if (error) throw error;
if (response) return handleCompileResponse(response);
}

if (error) throw error;
if (response) return handleCompileResponse(response);
} finally {
activeDeprecationOptions.delete(optionsKey);
}
}

Expand Down
126 changes: 123 additions & 3 deletions lib/src/deprecations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import {DeprecationOrId} from './vendor/sass';
import {Deprecation, DeprecationOrId, Options} from './vendor/sass';
import {Version} from './version';

export {deprecations} from './vendor/deprecations';
Expand All @@ -15,12 +15,132 @@ export {Deprecation, DeprecationOrId, DeprecationStatus} from './vendor/sass';
export function getDeprecationIds(
arr: (DeprecationOrId | Version)[]
): string[] {
return arr.flatMap(item => {
return arr.map(item => {
if (item instanceof Version) {
return arr.map(item => item.toString());
return item.toString();
} else if (typeof item === 'string') {
return item;
}
return item.id;
});
}

/**
* Map between active compilations and the deprecation options they use.
*
* This is used to determine which options to use when handling host-side
* deprecation warnings that aren't explicitly tied to a particular compilation.
*/
export const activeDeprecationOptions: Map<Object, DeprecationOptions> =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're using Symbols as the key here, you could actually just use a Record<Symbol, DeprecationOptions>. Either way, the key type should be Symbol.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed the key type to Symbol, but keeping this as a Map because it's better for adding/removing and checking the size

new Map();

/**
* Shorthand for the subset of options related to deprecations.
*/
export type DeprecationOptions = Pick<
Options<'sync'>,
'fatalDeprecations' | 'futureDeprecations' | 'silenceDeprecations'
>;

/**
* Handles a host-side deprecation warning, either emitting a warning, throwing
* an error, or doing nothing depending on the deprecation options used.
*
* If no specific deprecation options are passed here, then options will be
* determined based on the options of the active compilations.
*/
export function warnForHostSideDeprecation(
message: string,
deprecation: Deprecation,
options?: DeprecationOptions
): void {
if (
deprecation.status === 'future' &&
!isEnabledFuture(deprecation, options)
) {
return;
}
const fullMessage = `Deprecation [${deprecation.id}]: ${message}`;
if (isFatal(deprecation, options)) {
throw Error(fullMessage);
}
if (!isSilent(deprecation, options)) {
console.warn(fullMessage);
}
}

/**
* Checks whether the given deprecation is included in the given list of silent
* deprecations or is silenced by at least one active compilation.
*/
function isSilent(
deprecation: Deprecation,
options?: DeprecationOptions
): boolean {
if (!options) {
for (const potentialOptions of activeDeprecationOptions.values()) {
if (isSilent(deprecation, potentialOptions)) return true;
}
return false;
}
return getDeprecationIds(options?.silenceDeprecations ?? []).includes(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return getDeprecationIds(options?.silenceDeprecations ?? []).includes(
return getDeprecationIds(options.silenceDeprecations ?? []).includes(

There's no way for options to be undefined here. Also below.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

deprecation.id
);
}

/**
* Checks whether the given deprecation is included in the given list of future
* deprecations that should be enabled or is enabled in all active compilations.
*/
function isEnabledFuture(
deprecation: Deprecation,
options?: DeprecationOptions
): boolean {
if (!options) {
for (const potentialOptions of activeDeprecationOptions.values()) {
if (!isEnabledFuture(deprecation, potentialOptions)) return false;
}
return activeDeprecationOptions.size > 0;
}
return getDeprecationIds(options?.futureDeprecations ?? []).includes(
deprecation.id
);
}

/**
* Checks whether the given deprecation is included in the given list of
* fatal deprecations or is marked as fatal in all active compilations.
*/
function isFatal(
deprecation: Deprecation,
options?: DeprecationOptions
): boolean {
if (!options) {
for (const potentialOptions of activeDeprecationOptions.values()) {
if (!isFatal(deprecation, potentialOptions)) return false;
}
return activeDeprecationOptions.size > 0;
}
const versionNumber =
deprecation.deprecatedIn === null
? null
: deprecation.deprecatedIn.major * 1000000 +
deprecation.deprecatedIn.minor * 1000 +
deprecation.deprecatedIn.patch;
for (const fatal of options?.fatalDeprecations ?? []) {
if (fatal instanceof Version) {
if (versionNumber === null) continue;
if (
versionNumber <=
fatal.major * 1000000 + fatal.minor * 1000 + fatal.patch
) {
return true;
}
} else if (typeof fatal === 'string') {
if (fatal === deprecation.id) return true;
} else {
if ((fatal as Deprecation).id === deprecation.id) return true;
}
}
return false;
}
11 changes: 11 additions & 0 deletions lib/src/legacy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
compileString,
compileStringAsync,
} from '../compile';
import {deprecations, warnForHostSideDeprecation} from '../deprecations';
import {
SyncBoolean,
fileUrlToPathCrossPlatform,
Expand Down Expand Up @@ -50,6 +51,11 @@ export function render(
options = adjustOptions(options);

const start = Date.now();
warnForHostSideDeprecation(
'The legacy JS API is deprecated and will be removed in Dart Sass 2.0.0.',
deprecations['legacy-js-api'],
options
);
const compileSass = isStringOptions(options)
? compileStringAsync(options.data, convertStringOptions(options, false))
: compileAsync(options.file, convertOptions(options, false));
Expand All @@ -68,6 +74,11 @@ export function renderSync(options: LegacyOptions<'sync'>): LegacyResult {
const start = Date.now();
try {
options = adjustOptions(options);
warnForHostSideDeprecation(
'The legacy JS API is deprecated and will be removed in Dart Sass 2.0.0.',
deprecations['legacy-js-api'],
options
);
const result = isStringOptions(options)
? compileString(options.data, convertStringOptions(options, true))
: compile(options.file, convertOptions(options, true));
Expand Down
Loading