diff --git a/deno.json b/deno.json index 33694026e612..d2102bdace13 100644 --- a/deno.json +++ b/deno.json @@ -12,7 +12,7 @@ "automation/": "https://raw.githubusercontent.com/denoland/automation/0.10.0/" }, "tasks": { - "test": "deno test --doc --unstable --allow-all --parallel --coverage --trace-ops", + "test": "DENO_NO_DEPRECATION_WARNINGS=1 deno test --doc --unstable --allow-all --parallel --coverage --trace-ops", "test:browser": "git grep --name-only \"This module is browser compatible.\" | grep -v deno.json | grep -v .github/workflows | grep -v _tools | xargs deno check --config browser-compat.tsconfig.json", "fmt:licence-headers": "deno run --allow-read --allow-write ./_tools/check_licence.ts", "lint:deprecations": "deno run --allow-read --allow-net --allow-env=HOME ./_tools/check_deprecation.ts", diff --git a/internal/mod.ts b/internal/mod.ts new file mode 100644 index 000000000000..33d4eddc3c78 --- /dev/null +++ b/internal/mod.ts @@ -0,0 +1,9 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +/** + * Internal utilities for the public API of the Deno Standard Library. + * + * Note: for internal use only. + * + * @module + */ diff --git a/internal/warn_on_deprecated_api.ts b/internal/warn_on_deprecated_api.ts new file mode 100644 index 000000000000..0b606a54f34c --- /dev/null +++ b/internal/warn_on_deprecated_api.ts @@ -0,0 +1,97 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +// deno-lint-ignore no-explicit-any +const { Deno } = globalThis as any; + +const ALREADY_WARNED_DEPRECATED = new Set(); +const ENV_VAR_KEY = "DENO_NO_DEPRECATION_WARNINGS"; +const shouldDisableDeprecatedApiWarning = + Deno?.permissions.querySync?.({ name: "env", variable: ENV_VAR_KEY }) + .state === "granted" && Deno?.env.get(ENV_VAR_KEY) === "1"; + +interface WarnDeprecatedApiConfig { + /** The name of the deprecated API. */ + apiName: string; + /** The stack trace of the deprecated API. */ + stack: string; + /** The version in which the API will be removed. */ + removalVersion: string; + /** An optional message to print. */ + suggestion?: string; +} + +/** + * Prints a warning message to the console for the given deprecated API. + * + * These warnings can be disabled by setting `DENO_NO_DEPRECATION_WARNINGS=1` + * in the current process. + * + * Mostly copied from + * {@link https://github.com/denoland/deno/blob/c62615bfe5a070c2517f3af3208d4308c72eb054/runtime/js/99_main.js#L101}. + */ +export function warnOnDeprecatedApi(config: WarnDeprecatedApiConfig) { + if (shouldDisableDeprecatedApiWarning) return; + + const key = config.apiName + config.stack; + if (ALREADY_WARNED_DEPRECATED.has(key)) return; + + // If we haven't warned yet, let's do some processing of the stack trace + // to make it more useful. + const stackLines = config.stack.split("\n"); + stackLines.shift(); + + let isFromRemoteDependency = false; + const firstStackLine = stackLines[0]; + if (firstStackLine && !firstStackLine.includes("file:")) { + isFromRemoteDependency = true; + } + + ALREADY_WARNED_DEPRECATED.add(key); + console.log( + "%cWarning", + "color: yellow; font-weight: bold;", + ); + console.log( + `%c\u251c Use of deprecated "${config.apiName}" API.`, + "color: yellow;", + ); + console.log("%c\u2502", "color: yellow;"); + console.log( + `%c\u251c This API will be removed in version ${config.removalVersion} of the Deno Standard Library.`, + "color: yellow;", + ); + console.log("%c\u2502", "color: yellow;"); + console.log( + `%c\u251c Suggestion: ${config.suggestion}`, + "color: yellow;", + ); + if (isFromRemoteDependency) { + console.log("%c\u2502", "color: yellow;"); + console.log( + `%c\u251c Suggestion: It appears this API is used by a remote dependency.`, + "color: yellow;", + ); + console.log( + "%c\u2502 Try upgrading to the latest version of that dependency.", + "color: yellow;", + ); + } + + console.log("%c\u2502", "color: yellow;"); + console.log( + "%c\u251c Set `DENO_NO_DEPRECATION_WARNINGS=1` to disable these deprecation warnings.", + "color: yellow;", + ); + console.log("%c\u2502", "color: yellow;"); + console.log("%c\u2514 Stack trace:", "color: yellow;"); + for (let i = 0; i < stackLines.length; i++) { + console.log( + `%c ${i == stackLines.length - 1 ? "\u2514" : "\u251c"}\u2500 ${ + stackLines[i].trim() + }`, + "color: yellow;", + ); + } + console.log(); +} diff --git a/internal/warn_on_deprecated_api_test.ts b/internal/warn_on_deprecated_api_test.ts new file mode 100644 index 000000000000..e423f4b7fdc0 --- /dev/null +++ b/internal/warn_on_deprecated_api_test.ts @@ -0,0 +1,49 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { assertEquals } from "../assert/assert_equals.ts"; +import { warnOnDeprecatedApi } from "./warn_on_deprecated_api.ts"; + +Deno.test("warnDeprecatedApi()", async () => { + const command = new Deno.Command(Deno.execPath(), { + args: ["run", "--quiet", "--no-lock", import.meta.url], + stderr: "inherit", + }); + const { success, stdout } = await command.output(); + const output = new TextDecoder().decode(stdout); + + assertEquals(success, true); + assertEquals( + output, + `Warning +├ Use of deprecated "fn()" API. +│ +├ This API will be removed in version 1.0.0 of the Deno Standard Library. +│ +├ Suggestion: Do something else instead. +│ +├ Set \`DENO_NO_DEPRECATION_WARNINGS=1\` to disable these deprecation warnings. +│ +└ Stack trace: + ├─ at fn (${import.meta.url}:39:12) + └─ at ${import.meta.url}:47:31 + +Hello, world! +Hello, world! +END +`, + ); +}); + +function fn() { + warnOnDeprecatedApi({ + apiName: "fn()", + stack: new Error().stack!, + removalVersion: "1.0.0", + suggestion: "Do something else instead.", + }); + console.log("Hello, world!"); +} + +if (import.meta.main) { + for (let i = 0; i < 2; i++) fn(); + console.log("END"); +} diff --git a/streams/read_all.ts b/streams/read_all.ts index 3e04a97b80b9..72949364e27b 100644 --- a/streams/read_all.ts +++ b/streams/read_all.ts @@ -6,6 +6,7 @@ import { readAllSync as _readAllSync, } from "../io/read_all.ts"; import type { Reader, ReaderSync } from "../io/types.ts"; +import { warnOnDeprecatedApi } from "../internal/warn_on_deprecated_api.ts"; /** * Read {@linkcode Reader} `r` until EOF (`null`) and resolve to the content as @@ -33,6 +34,12 @@ import type { Reader, ReaderSync } from "../io/types.ts"; * @deprecated (will be removed in 0.214.0) Import from {@link https://deno.land/std/io/read_all.ts} instead. */ export async function readAll(r: Reader): Promise { + warnOnDeprecatedApi({ + apiName: "readAll()", + stack: new Error().stack!, + removalVersion: "0.214.0", + suggestion: "Import from `https://deno.land/std/io/read_all.ts` instead.", + }); return await _readAll(r); } @@ -62,5 +69,11 @@ export async function readAll(r: Reader): Promise { * @deprecated (will be removed in 0.214.0) Import from {@link https://deno.land/std/io/read_all.ts} instead. */ export function readAllSync(r: ReaderSync): Uint8Array { + warnOnDeprecatedApi({ + apiName: "readAllSync()", + stack: new Error().stack!, + removalVersion: "0.214.0", + suggestion: "Import from `https://deno.land/std/io/read_all.ts` instead.", + }); return _readAllSync(r); }