-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathdeprecate.ts
173 lines (153 loc) · 4.82 KB
/
deprecate.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/**
* Marks a function as deprecated and emit warnings when it is called.
* @module
* @experimental
*/
import runtime, { RuntimeInfo } from "./runtime.ts";
import wrap from "./wrap.ts";
const warnedRecord = new Map<Function, boolean>();
/**
* Marks a function as deprecated and returns a wrapped function.
*
* When the wrapped function is called, a deprecation warning will be emitted
* to the stdout.
*
* **NOTE:** The original function must have a name.
*
* **NOTE:** This function is **experimental** and the warning may point to the
* wrong file source in some environments.
*
* @param tip Extra tip for the user to migrate.
* @param once If set, the warning will only be emitted once.
*
* @example
* ```ts
* import deprecate from "@ayonli/jsext/deprecate";
*
* const sum = deprecate(function sum(a: number, b: number) {
* return a + b;
* }, "use `a + b` instead");
* console.log(sum(1, 2));
* // output:
* // DeprecationWarning: sum() is deprecated, use `a + b` instead (at <anonymous>:4:13)
* // 3
* ```
*/
export default function deprecate<T, Fn extends (this: T, ...args: any[]) => any>(
fn: Fn,
tip?: string,
once?: boolean
): Fn;
/**
* Emits a deprecation warning for the target, usually a parameter, an option,
* or the function's name, etc.
*
* @param forFn Usually set to the current function, used to locate the call-site.
* @param tip Extra tip for the user to migrate.
* @param once If set, the warning will only be emitted once.
*
* @example
* ```ts
* import deprecate from "@ayonli/jsext/deprecate";
*
* function pow(a: number, b: number) {
* deprecate("pow()", pow, "use `a ** b` instead");
* return a ** b;
* }
*
* console.log(pow(2, 3));
* // output:
* // DeprecationWarning: pow() is deprecated, use `a ** b` instead (at <anonymous>:5:13)
* // 3
* ```
*/
export default function deprecate(target: string, forFn: Function, tip?: string, once?: boolean): void;
export default function deprecate<T, Fn extends (this: T, ...args: any[]) => any>(
target: Fn | string,
...args: any[]
): Fn | void {
const { identity } = runtime();
if (typeof target === "function") {
const tip = (args[0] as string) ?? "";
const once = (args[1] as boolean) ?? false;
return wrap<T, Fn>(target, function wrapped(fn, ...args) {
const lineOffset = ({
"node": 2,
"deno": 2,
"chrome": 2,
"workerd": 2,
"bun": 1,
"safari": 1,
"firefox": 3,
"fastly": 3,
"unknown": 3,
})[identity]!;
emitWarning(fn.name + "()", wrapped, tip, once, lineOffset, identity, true);
return fn.apply(this, args);
});
}
const forFn = args[0] as Function;
const tip = (args[1] as string) ?? "";
const once = (args[2] as boolean) ?? false;
const lineOffset = ({
"node": 1,
"deno": 1,
"chrome": 1,
"workerd": 1,
"bun": 1,
"safari": 1,
"firefox": 3,
"fastly": 3,
"unknown": 3,
})[identity]!;
return emitWarning(target, forFn, tip, once, lineOffset, identity, false);
}
function emitWarning(
target: string,
forFn: Function,
tip: string,
once: boolean,
lineNum: number,
runtime: RuntimeInfo["identity"],
wrapped = false
) {
if (!once || !warnedRecord.has(forFn)) {
let trace: { stack?: string; } = {};
if (typeof Error.captureStackTrace === "function") {
Error.captureStackTrace(trace, forFn);
} else {
trace = new Error("");
}
let lines = (trace.stack as string).split("\n");
const offset = lines.findIndex(line => line === "Error");
if (offset !== -1 && offset !== 0) {
lines = lines.slice(offset); // fix for tsx in Node.js v16
}
let line: string | undefined;
if (runtime === "safari") {
line = lines.find(line => line.trim().startsWith("module code@"))
|| lines[lineNum];
} else if (runtime === "bun" && !wrapped) {
line = lines.find(line => line.trim().startsWith("at module code"))
|| lines[lineNum];
} else {
line = lines[lineNum];
}
let warning = `${target} is deprecated`;
if (tip) {
warning += ", " + tip;
}
if (line) {
line = line.trim();
let start = line.indexOf("(");
if (start !== -1) {
start += 1;
const end = line.indexOf(")", start);
line = line.slice(start, end);
}
warning += " (" + line + ")";
}
console.warn("DeprecationWarning:", warning);
once && warnedRecord.set(forFn, true);
}
}