diff --git a/lib/index.mjs b/lib/index.mjs index c988be20..bd54c4a3 100644 --- a/lib/index.mjs +++ b/lib/index.mjs @@ -9,6 +9,8 @@ export const AsyncCompiler = sass.AsyncCompiler; export const Compiler = sass.Compiler; export const initAsyncCompiler = sass.initAsyncCompiler; export const initCompiler = sass.initCompiler; +export const deprecations = sass.deprecations; +export const Version = sass.Version; export const Logger = sass.Logger; export const CalculationInterpolation = sass.CalculationInterpolation; export const CalculationOperation = sass.CalculationOperation; @@ -86,6 +88,14 @@ export default { defaultExportDeprecation(); return sass.Compiler; }, + get deprecations() { + defaultExportDeprecation(); + return sass.deprecations; + }, + get Version() { + defaultExportDeprecation(); + return sass.Version; + }, get Logger() { defaultExportDeprecation(); return sass.Logger; diff --git a/lib/index.ts b/lib/index.ts index da4965d5..96ce209b 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -35,6 +35,13 @@ export { } from './src/compile'; export {initAsyncCompiler, AsyncCompiler} from './src/compiler/async'; export {initCompiler, Compiler} from './src/compiler/sync'; +export { + deprecations, + Deprecation, + DeprecationOrId, + DeprecationStatus, + Version, +} from './src/deprecations'; export {render, renderSync} from './src/legacy'; export const info = `sass-embedded\t${pkg.version}`; diff --git a/lib/src/compiler/utils.ts b/lib/src/compiler/utils.ts index 135de80f..09fa031d 100644 --- a/lib/src/compiler/utils.ts +++ b/lib/src/compiler/utils.ts @@ -4,6 +4,7 @@ import * as p from 'path'; import * as supportsColor from 'supports-color'; +import {deprecations, getDeprecationIds, Deprecation} from '../deprecations'; import {deprotofySourceSpan} from '../deprotofy-span'; import {Dispatcher, DispatcherHandlers} from '../dispatcher'; import {Exception} from '../exception'; @@ -75,6 +76,9 @@ function newCompileRequest( verbose: !!options?.verbose, charset: !!(options?.charset ?? true), silent: options?.logger === Logger.silent, + fatalDeprecation: getDeprecationIds(options?.fatalDeprecations ?? []), + silenceDeprecation: getDeprecationIds(options?.silenceDeprecations ?? []), + futureDeprecation: getDeprecationIds(options?.futureDeprecations ?? []), }); switch (options?.style ?? 'expanded') { @@ -137,6 +141,13 @@ export function newCompileStringRequest( return request; } +/** Type guard to check that `id` is a valid deprecation ID. */ +function validDeprecationId( + id: string | number | symbol | undefined +): id is keyof typeof deprecations { + return !!id && id in deprecations; +} + /** Handles a log event according to `options`. */ export function handleLogEvent( options: OptionsWithLegacy<'sync' | 'async'> | undefined, @@ -148,6 +159,9 @@ export function handleLogEvent( if (options?.legacy) message = removeLegacyImporter(message); let formatted = event.formatted; if (options?.legacy) formatted = removeLegacyImporter(formatted); + const deprecationType = validDeprecationId(event.deprecationType) + ? deprecations[event.deprecationType] + : null; if (event.type === proto.LogEventType.DEBUG) { if (options?.logger?.debug) { @@ -159,10 +173,18 @@ export function handleLogEvent( } } else { if (options?.logger?.warn) { - const params: {deprecation: boolean; span?: SourceSpan; stack?: string} = - { - deprecation: event.type === proto.LogEventType.DEPRECATION_WARNING, - }; + const params: ( + | { + deprecation: true; + deprecationType: Deprecation; + } + | {deprecation: false} + ) & { + span?: SourceSpan; + stack?: string; + } = deprecationType + ? {deprecation: true, deprecationType: deprecationType} + : {deprecation: false}; if (span) params.span = span; const stack = event.stackTrace; diff --git a/lib/src/deprecations.ts b/lib/src/deprecations.ts new file mode 100644 index 00000000..7fe782d3 --- /dev/null +++ b/lib/src/deprecations.ts @@ -0,0 +1,173 @@ +// Copyright 2024 Google LLC. Use of this source code is governed by an +// MIT-style license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import * as api from './vendor/sass'; + +export {Deprecation, DeprecationOrId, DeprecationStatus} from './vendor/sass'; + +export class Version implements api.Version { + constructor( + readonly major: number, + readonly minor: number, + readonly patch: number + ) {} + static parse(version: string): Version { + const match = version.match(/^(\d+)\.(\d+)\.(\d+)$/); + if (match === null) { + throw new Error(`Invalid version ${version}`); + } + return new Version( + parseInt(match[1]), + parseInt(match[2]), + parseInt(match[3]) + ); + } +} + +/** + * Returns whether the given deprecation was active in the given version. + */ +function isActiveIn(deprecation: api.Deprecation, version: Version) { + const deprecatedIn = deprecation.deprecatedIn; + if (deprecation.status !== 'active' || !deprecatedIn) return false; + if (version.major > deprecatedIn.major) return true; + if (version.major < deprecatedIn.major) return false; + if (version.minor > deprecatedIn.minor) return true; + if (version.minor < deprecatedIn.minor) return false; + return version.patch >= deprecatedIn.patch; +} + +/** + * Converts a mixed array of deprecations, IDs, and versions to an array of IDs + * that's ready to include in a CompileRequest. + */ +export function getDeprecationIds( + arr: (api.DeprecationOrId | Version)[] +): string[] { + return arr.flatMap(item => { + if (item instanceof Version) { + return Object.values(deprecations) + .filter(deprecation => isActiveIn(deprecation, item)) + .map(deprecation => deprecation.id); + } else if (typeof item === 'string') { + return item; + } + return item.id; + }); +} + +export const deprecations: typeof api.deprecations = { + 'call-string': { + id: 'call-string', + status: 'active', + deprecatedIn: new Version(0, 0, 0), + obsoleteIn: null, + description: 'Passing a string directly to meta.call().', + }, + elseif: { + id: 'elseif', + status: 'active', + deprecatedIn: new Version(1, 3, 2), + obsoleteIn: null, + description: '@elseif.', + }, + 'moz-document': { + id: 'moz-document', + status: 'active', + deprecatedIn: new Version(1, 7, 2), + obsoleteIn: null, + description: '@-moz-document.', + }, + 'relative-canonical': { + id: 'relative-canonical', + status: 'active', + deprecatedIn: new Version(1, 14, 2), + obsoleteIn: null, + }, + 'new-global': { + id: 'new-global', + status: 'active', + deprecatedIn: new Version(1, 17, 2), + obsoleteIn: null, + description: 'Declaring new variables with !global.', + }, + 'color-module-compat': { + id: 'color-module-compat', + status: 'active', + deprecatedIn: new Version(1, 23, 0), + obsoleteIn: null, + description: + 'Using color module functions in place of plain CSS functions.', + }, + 'slash-div': { + id: 'slash-div', + status: 'active', + deprecatedIn: new Version(1, 33, 0), + obsoleteIn: null, + description: '/ operator for division.', + }, + 'bogus-combinators': { + id: 'bogus-combinators', + status: 'active', + deprecatedIn: new Version(1, 54, 0), + obsoleteIn: null, + description: 'Leading, trailing, and repeated combinators.', + }, + 'strict-unary': { + id: 'strict-unary', + status: 'active', + deprecatedIn: new Version(1, 55, 0), + obsoleteIn: null, + description: 'Ambiguous + and - operators.', + }, + 'function-units': { + id: 'function-units', + status: 'active', + deprecatedIn: new Version(1, 56, 0), + obsoleteIn: null, + description: 'Passing invalid units to built-in functions.', + }, + 'duplicate-var-flags': { + id: 'duplicate-var-flags', + status: 'active', + deprecatedIn: new Version(1, 62, 0), + obsoleteIn: null, + description: 'Using !default or !global multiple times for one variable.', + }, + 'null-alpha': { + id: 'null-alpha', + status: 'active', + deprecatedIn: new Version(1, 62, 3), + obsoleteIn: null, + description: 'Passing null as alpha in the JS API.', + }, + 'abs-percent': { + id: 'abs-percent', + status: 'active', + deprecatedIn: new Version(1, 65, 0), + obsoleteIn: null, + description: 'Passing percentages to the Sass abs() function.', + }, + 'fs-importer-cwd': { + id: 'fs-importer-cwd', + status: 'active', + deprecatedIn: new Version(1, 73, 0), + obsoleteIn: null, + description: + 'Using the current working directory as an implicit load path.', + }, + import: { + id: 'import', + status: 'future', + deprecatedIn: null, + obsoleteIn: null, + description: '@import rules.', + }, + 'user-authored': { + id: 'user-authored', + status: 'user', + deprecatedIn: null, + obsoleteIn: null, + }, +}; diff --git a/lib/src/importer-registry.ts b/lib/src/importer-registry.ts index 7208950a..9a1cb337 100644 --- a/lib/src/importer-registry.ts +++ b/lib/src/importer-registry.ts @@ -21,12 +21,12 @@ export class NodePackageImporter { entryPointDirectory = entryPointDirectory ? p.resolve(entryPointDirectory) : require.main?.filename - ? p.dirname(require.main.filename) - : // TODO: Find a way to use `import.meta.main` once - // https://github.com/nodejs/node/issues/49440 is done. - process.argv[1] - ? createRequire(process.argv[1]).resolve(process.argv[1]) - : undefined; + ? p.dirname(require.main.filename) + : // TODO: Find a way to use `import.meta.main` once + // https://github.com/nodejs/node/issues/49440 is done. + process.argv[1] + ? createRequire(process.argv[1]).resolve(process.argv[1]) + : undefined; if (!entryPointDirectory) { throw new Error( 'The Node package importer cannot determine an entry point ' + diff --git a/lib/src/legacy/importer.ts b/lib/src/legacy/importer.ts index 791cfc12..97ef9117 100644 --- a/lib/src/legacy/importer.ts +++ b/lib/src/legacy/importer.ts @@ -216,8 +216,8 @@ export class LegacyImporterWrapper const syntax = canonicalUrl.pathname.endsWith('.sass') ? 'indented' : canonicalUrl.pathname.endsWith('.css') - ? 'css' - : 'scss'; + ? 'css' + : 'scss'; let contents = this.lastContents ?? diff --git a/package.json b/package.json index bb6ff25a..f64b8eb6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "sass-embedded", "version": "1.72.0", - "protocol-version": "2.5.0", + "protocol-version": "2.6.0", "compiler-version": "1.72.0", "description": "Node.js library that communicates with Embedded Dart Sass using the Embedded Sass protocol", "repository": "sass/embedded-host-node",