@logtape/lint 2.2.0-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/dist/core/ast.cjs +517 -0
- package/dist/core/ast.js +506 -0
- package/dist/core/ast.js.map +1 -0
- package/dist/deno/plugin.cjs +332 -0
- package/dist/deno/plugin.d.cts +46 -0
- package/dist/deno/plugin.d.cts.map +1 -0
- package/dist/deno/plugin.d.ts +46 -0
- package/dist/deno/plugin.d.ts.map +1 -0
- package/dist/deno/plugin.js +333 -0
- package/dist/deno/plugin.js.map +1 -0
- package/dist/eslint/plugin.cjs +51 -0
- package/dist/eslint/plugin.d.cts +26 -0
- package/dist/eslint/plugin.d.cts.map +1 -0
- package/dist/eslint/plugin.d.ts +26 -0
- package/dist/eslint/plugin.d.ts.map +1 -0
- package/dist/eslint/plugin.js +44 -0
- package/dist/eslint/plugin.js.map +1 -0
- package/dist/mod.cjs +15 -0
- package/dist/mod.d.cts +6 -0
- package/dist/mod.d.ts +6 -0
- package/dist/mod.js +7 -0
- package/dist/rules/no-message-interpolation.cjs +52 -0
- package/dist/rules/no-message-interpolation.d.cts +23 -0
- package/dist/rules/no-message-interpolation.d.cts.map +1 -0
- package/dist/rules/no-message-interpolation.d.ts +23 -0
- package/dist/rules/no-message-interpolation.d.ts.map +1 -0
- package/dist/rules/no-message-interpolation.js +53 -0
- package/dist/rules/no-message-interpolation.js.map +1 -0
- package/dist/rules/no-unawaited-log.cjs +67 -0
- package/dist/rules/no-unawaited-log.d.cts +21 -0
- package/dist/rules/no-unawaited-log.d.cts.map +1 -0
- package/dist/rules/no-unawaited-log.d.ts +21 -0
- package/dist/rules/no-unawaited-log.d.ts.map +1 -0
- package/dist/rules/no-unawaited-log.js +68 -0
- package/dist/rules/no-unawaited-log.js.map +1 -0
- package/dist/rules/prefer-lazy-evaluation.cjs +59 -0
- package/dist/rules/prefer-lazy-evaluation.d.cts +22 -0
- package/dist/rules/prefer-lazy-evaluation.d.cts.map +1 -0
- package/dist/rules/prefer-lazy-evaluation.d.ts +22 -0
- package/dist/rules/prefer-lazy-evaluation.d.ts.map +1 -0
- package/dist/rules/prefer-lazy-evaluation.js +60 -0
- package/dist/rules/prefer-lazy-evaluation.js.map +1 -0
- package/dist/rules/require-meta-sink.cjs +75 -0
- package/dist/rules/require-meta-sink.d.cts +27 -0
- package/dist/rules/require-meta-sink.d.cts.map +1 -0
- package/dist/rules/require-meta-sink.d.ts +27 -0
- package/dist/rules/require-meta-sink.d.ts.map +1 -0
- package/dist/rules/require-meta-sink.js +76 -0
- package/dist/rules/require-meta-sink.js.map +1 -0
- package/dist/utils.cjs +82 -0
- package/dist/utils.js +82 -0
- package/dist/utils.js.map +1 -0
- package/package.json +90 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
const require_ast = require('../core/ast.cjs');
|
|
2
|
+
|
|
3
|
+
//#region src/deno/plugin.ts
|
|
4
|
+
function extractIdentifiers(node, names) {
|
|
5
|
+
if (!node) return;
|
|
6
|
+
if (node.type === "Identifier") names.add(node.name);
|
|
7
|
+
else if (node.type === "ObjectPattern") {
|
|
8
|
+
for (const prop of node.properties ?? []) if (prop.type === "Property") extractIdentifiers(prop.value, names);
|
|
9
|
+
else if (prop.type === "RestElement") extractIdentifiers(prop.argument, names);
|
|
10
|
+
} else if (node.type === "ArrayPattern") for (const elem of node.elements ?? []) extractIdentifiers(elem, names);
|
|
11
|
+
else if (node.type === "AssignmentPattern") extractIdentifiers(node.left, names);
|
|
12
|
+
else if (node.type === "RestElement") extractIdentifiers(node.argument, names);
|
|
13
|
+
else if (node.type === "TSParameterProperty") extractIdentifiers(node.parameter, names);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Creates a scope tracker for LogTape logger variable bindings.
|
|
17
|
+
*
|
|
18
|
+
* The Deno Lint plugin API does not expose a scope manager, so this hand-rolls
|
|
19
|
+
* one with a scope stack (Map<name, isLogger>) to correctly handle:
|
|
20
|
+
* - Function parameters that shadow logger names (tracked as false)
|
|
21
|
+
* - Local variables that re-declare logger names with non-logger values
|
|
22
|
+
* - Block scoping via BlockStatement enter/exit
|
|
23
|
+
*
|
|
24
|
+
* Returns a `visitors` object to spread into a rule's visitor map, plus the
|
|
25
|
+
* scope-aware predicates the rule's `CallExpression` handler needs.
|
|
26
|
+
*/
|
|
27
|
+
function makeLoggerScope() {
|
|
28
|
+
const getterNames = /* @__PURE__ */ new Set();
|
|
29
|
+
const lazyNames = /* @__PURE__ */ new Set();
|
|
30
|
+
const scopeStack = [/* @__PURE__ */ new Map()];
|
|
31
|
+
const shadowedGetterStack = [/* @__PURE__ */ new Set()];
|
|
32
|
+
const shadowedLazyStack = [/* @__PURE__ */ new Set()];
|
|
33
|
+
const asyncFnStack = [/* @__PURE__ */ new Map()];
|
|
34
|
+
function pushScope() {
|
|
35
|
+
scopeStack.push(/* @__PURE__ */ new Map());
|
|
36
|
+
shadowedGetterStack.push(/* @__PURE__ */ new Set());
|
|
37
|
+
shadowedLazyStack.push(/* @__PURE__ */ new Set());
|
|
38
|
+
asyncFnStack.push(/* @__PURE__ */ new Map());
|
|
39
|
+
}
|
|
40
|
+
function popScope() {
|
|
41
|
+
if (scopeStack.length > 1) scopeStack.pop();
|
|
42
|
+
if (shadowedGetterStack.length > 1) shadowedGetterStack.pop();
|
|
43
|
+
if (shadowedLazyStack.length > 1) shadowedLazyStack.pop();
|
|
44
|
+
if (asyncFnStack.length > 1) asyncFnStack.pop();
|
|
45
|
+
}
|
|
46
|
+
function isLoggerName(name) {
|
|
47
|
+
for (let i = scopeStack.length - 1; i >= 0; i--) if (scopeStack[i].has(name)) return scopeStack[i].get(name);
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
function isAsyncFunctionName(name) {
|
|
51
|
+
for (let i = asyncFnStack.length - 1; i >= 0; i--) if (asyncFnStack[i].has(name)) return asyncFnStack[i].get(name);
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
function isGetterShadowed(name) {
|
|
55
|
+
for (let i = shadowedGetterStack.length - 1; i >= 0; i--) if (shadowedGetterStack[i].has(name)) return true;
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
function isLazyShadowed(name) {
|
|
59
|
+
for (let i = shadowedLazyStack.length - 1; i >= 0; i--) if (shadowedLazyStack[i].has(name)) return true;
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
function recordImportShadow(name) {
|
|
63
|
+
shadowedGetterStack[shadowedGetterStack.length - 1].add(name);
|
|
64
|
+
shadowedLazyStack[shadowedLazyStack.length - 1].add(name);
|
|
65
|
+
}
|
|
66
|
+
function effectiveLazyNames() {
|
|
67
|
+
if (lazyNames.size === 0) return lazyNames;
|
|
68
|
+
const result = /* @__PURE__ */ new Set();
|
|
69
|
+
for (const name of lazyNames) if (!isLazyShadowed(name)) result.add(name);
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
function handleFunctionEnter(node) {
|
|
73
|
+
if (node.type === "FunctionDeclaration" && node.id?.type === "Identifier") {
|
|
74
|
+
recordImportShadow(node.id.name);
|
|
75
|
+
scopeStack[scopeStack.length - 1].set(node.id.name, false);
|
|
76
|
+
asyncFnStack[asyncFnStack.length - 1].set(node.id.name, node.async === true || require_ast.isPromiseReturningCallback(node));
|
|
77
|
+
}
|
|
78
|
+
pushScope();
|
|
79
|
+
const names = /* @__PURE__ */ new Set();
|
|
80
|
+
if (node.type === "FunctionExpression" && node.id?.type === "Identifier") names.add(node.id.name);
|
|
81
|
+
for (const param of node.params ?? []) extractIdentifiers(param, names);
|
|
82
|
+
for (const name of names) {
|
|
83
|
+
scopeStack[scopeStack.length - 1].set(name, false);
|
|
84
|
+
recordImportShadow(name);
|
|
85
|
+
asyncFnStack[asyncFnStack.length - 1].set(name, false);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function handleBlockEnter(node) {
|
|
89
|
+
pushScope();
|
|
90
|
+
for (const stmt of node.body ?? []) if (stmt?.type === "FunctionDeclaration" && stmt.id?.type === "Identifier") {
|
|
91
|
+
recordImportShadow(stmt.id.name);
|
|
92
|
+
scopeStack[scopeStack.length - 1].set(stmt.id.name, false);
|
|
93
|
+
asyncFnStack[asyncFnStack.length - 1].set(stmt.id.name, stmt.async === true || require_ast.isPromiseReturningCallback(stmt));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function handleCatchEnter(node) {
|
|
97
|
+
pushScope();
|
|
98
|
+
if (!node.param) return;
|
|
99
|
+
const names = /* @__PURE__ */ new Set();
|
|
100
|
+
extractIdentifiers(node.param, names);
|
|
101
|
+
for (const name of names) {
|
|
102
|
+
scopeStack[scopeStack.length - 1].set(name, false);
|
|
103
|
+
recordImportShadow(name);
|
|
104
|
+
asyncFnStack[asyncFnStack.length - 1].set(name, false);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function isLoggerExpr(node, depth = 0) {
|
|
108
|
+
if (depth > 16 || !node) return false;
|
|
109
|
+
if (node.type === "CallExpression" && node.callee?.type === "Identifier" && getterNames.has(node.callee.name) && !isGetterShadowed(node.callee.name)) return true;
|
|
110
|
+
if (node.type === "CallExpression" && node.callee?.type === "MemberExpression" && !node.callee.computed && node.callee.property?.type === "Identifier" && (node.callee.property.name === "with" || node.callee.property.name === "getChild")) return isLoggerExpr(node.callee.object, depth + 1);
|
|
111
|
+
if (node.type === "Identifier") return isLoggerName(node.name);
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
function isLogtapeCallee(callee) {
|
|
115
|
+
if (!callee || callee.type !== "MemberExpression") return false;
|
|
116
|
+
return isLoggerExpr(callee.object);
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
visitors: {
|
|
120
|
+
ImportDeclaration(node) {
|
|
121
|
+
if (!require_ast.isLogtapeImportSource(node.source?.value)) return;
|
|
122
|
+
for (const spec of node.specifiers ?? []) {
|
|
123
|
+
if (spec.type !== "ImportSpecifier") continue;
|
|
124
|
+
if (spec.imported?.name === "getLogger") getterNames.add(spec.local?.name);
|
|
125
|
+
else if (spec.imported?.name === "lazy") lazyNames.add(spec.local?.name);
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
VariableDeclarator(node) {
|
|
129
|
+
if (node.id?.type === "Identifier") {
|
|
130
|
+
const name = node.id.name;
|
|
131
|
+
const init = node.init;
|
|
132
|
+
const isLogger = isLoggerExpr(init);
|
|
133
|
+
if (isLogger) scopeStack[scopeStack.length - 1].set(name, true);
|
|
134
|
+
else if (isLoggerName(name)) scopeStack[scopeStack.length - 1].set(name, false);
|
|
135
|
+
recordImportShadow(name);
|
|
136
|
+
asyncFnStack[asyncFnStack.length - 1].set(name, require_ast.isAsyncFunctionExpr(init) || require_ast.isPromiseReturningCallback(init));
|
|
137
|
+
} else {
|
|
138
|
+
const names = /* @__PURE__ */ new Set();
|
|
139
|
+
extractIdentifiers(node.id, names);
|
|
140
|
+
for (const name of names) {
|
|
141
|
+
if (isLoggerName(name)) scopeStack[scopeStack.length - 1].set(name, false);
|
|
142
|
+
recordImportShadow(name);
|
|
143
|
+
asyncFnStack[asyncFnStack.length - 1].set(name, false);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
FunctionDeclaration: handleFunctionEnter,
|
|
148
|
+
"FunctionDeclaration:exit": popScope,
|
|
149
|
+
FunctionExpression: handleFunctionEnter,
|
|
150
|
+
"FunctionExpression:exit": popScope,
|
|
151
|
+
ArrowFunctionExpression: handleFunctionEnter,
|
|
152
|
+
"ArrowFunctionExpression:exit": popScope,
|
|
153
|
+
Program: handleBlockEnter,
|
|
154
|
+
"Program:exit": popScope,
|
|
155
|
+
BlockStatement: handleBlockEnter,
|
|
156
|
+
"BlockStatement:exit": popScope,
|
|
157
|
+
TSModuleBlock: handleBlockEnter,
|
|
158
|
+
"TSModuleBlock:exit": popScope,
|
|
159
|
+
ForStatement: pushScope,
|
|
160
|
+
"ForStatement:exit": popScope,
|
|
161
|
+
ForInStatement: pushScope,
|
|
162
|
+
"ForInStatement:exit": popScope,
|
|
163
|
+
ForOfStatement: pushScope,
|
|
164
|
+
"ForOfStatement:exit": popScope,
|
|
165
|
+
SwitchStatement: pushScope,
|
|
166
|
+
"SwitchStatement:exit": popScope,
|
|
167
|
+
CatchClause: handleCatchEnter,
|
|
168
|
+
"CatchClause:exit": popScope
|
|
169
|
+
},
|
|
170
|
+
isLogtapeCallee,
|
|
171
|
+
isAsyncFunctionName,
|
|
172
|
+
lazyNames,
|
|
173
|
+
effectiveLazyNames
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Deno Lint plugin providing LogTape lint rules.
|
|
178
|
+
*
|
|
179
|
+
* > [!WARNING]
|
|
180
|
+
* > The Deno Lint plugin API is experimental. This plugin may break between
|
|
181
|
+
* > Deno releases while the API is stabilised.
|
|
182
|
+
*/
|
|
183
|
+
const logtapePlugin = {
|
|
184
|
+
name: "logtape",
|
|
185
|
+
rules: {
|
|
186
|
+
"no-message-interpolation": { create(ctx) {
|
|
187
|
+
const scope = makeLoggerScope();
|
|
188
|
+
return {
|
|
189
|
+
...scope.visitors,
|
|
190
|
+
CallExpression(node) {
|
|
191
|
+
if (!scope.isLogtapeCallee(node.callee)) return;
|
|
192
|
+
const methodName = require_ast.logMethodName(node.callee);
|
|
193
|
+
if (!methodName || !require_ast.LOG_METHODS.has(methodName)) return;
|
|
194
|
+
const firstArg = require_ast.unwrapTypeAssertion(node.arguments?.[0]);
|
|
195
|
+
if (!firstArg || firstArg.type !== "TemplateLiteral") return;
|
|
196
|
+
if (!firstArg.expressions?.length) return;
|
|
197
|
+
ctx.report({
|
|
198
|
+
node: firstArg,
|
|
199
|
+
message: "Avoid using template literal interpolation in log messages. Use a message template string with structured properties instead: logger.info(\"User {userId} logged in.\", { userId })."
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
} },
|
|
204
|
+
"prefer-lazy-evaluation": { create(ctx) {
|
|
205
|
+
const scope = makeLoggerScope();
|
|
206
|
+
return {
|
|
207
|
+
...scope.visitors,
|
|
208
|
+
CallExpression(node) {
|
|
209
|
+
if (!scope.isLogtapeCallee(node.callee)) return;
|
|
210
|
+
const methodName = require_ast.logMethodName(node.callee);
|
|
211
|
+
if (!methodName || !require_ast.LOG_METHODS.has(methodName)) return;
|
|
212
|
+
const selected = require_ast.selectLazyPropsObject(node.arguments);
|
|
213
|
+
if (!selected) return;
|
|
214
|
+
const { propsObject, fixTarget, propertiesOnly } = selected;
|
|
215
|
+
if (!require_ast.propsHaveEagerCall(propsObject, scope.effectiveLazyNames())) return;
|
|
216
|
+
const hasAsyncSyntax = require_ast.containsAwaitOrYield(propsObject);
|
|
217
|
+
ctx.report({
|
|
218
|
+
node: propsObject,
|
|
219
|
+
message: "Wrap the properties object in a lazy callback to avoid unnecessary computation: logger.debug(\"msg\", () => ({ ... })).",
|
|
220
|
+
fix: hasAsyncSyntax ? void 0 : (fixer) => {
|
|
221
|
+
const sourceCode = ctx.sourceCode ?? ctx.getSourceCode?.();
|
|
222
|
+
const text = typeof sourceCode === "string" && fixTarget.range ? sourceCode.slice(fixTarget.range[0], fixTarget.range[1]) : sourceCode?.getText?.(fixTarget);
|
|
223
|
+
if (!text) return null;
|
|
224
|
+
return fixer.replaceText(fixTarget, propertiesOnly ? `"{*}", () => (${text})` : `() => (${text})`);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
} },
|
|
230
|
+
"no-unawaited-log": { create(ctx) {
|
|
231
|
+
const scope = makeLoggerScope();
|
|
232
|
+
return {
|
|
233
|
+
...scope.visitors,
|
|
234
|
+
CallExpression(node) {
|
|
235
|
+
if (!scope.isLogtapeCallee(node.callee)) return;
|
|
236
|
+
const methodName = require_ast.logMethodName(node.callee);
|
|
237
|
+
if (!methodName || !require_ast.LOG_METHODS.has(methodName)) return;
|
|
238
|
+
const secondArg = require_ast.unwrapTypeAssertion(node.arguments?.[1]);
|
|
239
|
+
if (!secondArg) return;
|
|
240
|
+
const isAsyncCallback = require_ast.isAsyncFunctionExpr(secondArg) || require_ast.isPromiseReturningCallback(secondArg) || secondArg.type === "Identifier" && scope.isAsyncFunctionName(secondArg.name);
|
|
241
|
+
if (!isAsyncCallback) return;
|
|
242
|
+
if (require_ast.isLogPromiseHandled(node)) return;
|
|
243
|
+
ctx.report({
|
|
244
|
+
node,
|
|
245
|
+
message: "Async lazy callbacks must be awaited to ensure the log is flushed: await logger.debug(\"msg\", async () => ({ ... })).",
|
|
246
|
+
fix: require_ast.canInsertAwait(node) ? (fixer) => fixer.insertTextBefore(node, "await ") : void 0
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
} },
|
|
251
|
+
"require-meta-sink": { create(ctx) {
|
|
252
|
+
const configFns = /* @__PURE__ */ new Set();
|
|
253
|
+
const shadowedConfigScopes = [/* @__PURE__ */ new Set()];
|
|
254
|
+
function pushConfigScope() {
|
|
255
|
+
shadowedConfigScopes.push(/* @__PURE__ */ new Set());
|
|
256
|
+
}
|
|
257
|
+
function popConfigScope() {
|
|
258
|
+
if (shadowedConfigScopes.length > 1) shadowedConfigScopes.pop();
|
|
259
|
+
}
|
|
260
|
+
function isConfigShadowed(name) {
|
|
261
|
+
for (let i = shadowedConfigScopes.length - 1; i >= 0; i--) if (shadowedConfigScopes[i].has(name)) return true;
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
function handleFnEnter(node) {
|
|
265
|
+
if (node.type === "FunctionDeclaration" && node.id?.type === "Identifier" && configFns.has(node.id.name)) shadowedConfigScopes[shadowedConfigScopes.length - 1].add(node.id.name);
|
|
266
|
+
pushConfigScope();
|
|
267
|
+
const names = /* @__PURE__ */ new Set();
|
|
268
|
+
if (node.type === "FunctionExpression" && node.id?.type === "Identifier") names.add(node.id.name);
|
|
269
|
+
for (const param of node.params ?? []) extractIdentifiers(param, names);
|
|
270
|
+
for (const name of names) if (configFns.has(name)) shadowedConfigScopes[shadowedConfigScopes.length - 1].add(name);
|
|
271
|
+
}
|
|
272
|
+
function handleBlockEnter(node) {
|
|
273
|
+
pushConfigScope();
|
|
274
|
+
for (const stmt of node.body ?? []) if (stmt?.type === "FunctionDeclaration" && stmt.id?.type === "Identifier") shadowedConfigScopes[shadowedConfigScopes.length - 1].add(stmt.id.name);
|
|
275
|
+
}
|
|
276
|
+
function handleCatchEnter(node) {
|
|
277
|
+
pushConfigScope();
|
|
278
|
+
if (!node.param) return;
|
|
279
|
+
const names = /* @__PURE__ */ new Set();
|
|
280
|
+
extractIdentifiers(node.param, names);
|
|
281
|
+
for (const name of names) if (configFns.has(name)) shadowedConfigScopes[shadowedConfigScopes.length - 1].add(name);
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
ImportDeclaration(node) {
|
|
285
|
+
if (!require_ast.isLogtapeImportSource(node.source?.value)) return;
|
|
286
|
+
for (const specifier of node.specifiers ?? []) {
|
|
287
|
+
if (specifier.type !== "ImportSpecifier") continue;
|
|
288
|
+
const imported = specifier.imported?.name;
|
|
289
|
+
if (imported === "configure" || imported === "configureSync") configFns.add(specifier.local?.name);
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
FunctionDeclaration: handleFnEnter,
|
|
293
|
+
"FunctionDeclaration:exit": popConfigScope,
|
|
294
|
+
FunctionExpression: handleFnEnter,
|
|
295
|
+
"FunctionExpression:exit": popConfigScope,
|
|
296
|
+
ArrowFunctionExpression: handleFnEnter,
|
|
297
|
+
"ArrowFunctionExpression:exit": popConfigScope,
|
|
298
|
+
Program: handleBlockEnter,
|
|
299
|
+
"Program:exit": popConfigScope,
|
|
300
|
+
BlockStatement: handleBlockEnter,
|
|
301
|
+
"BlockStatement:exit": popConfigScope,
|
|
302
|
+
TSModuleBlock: handleBlockEnter,
|
|
303
|
+
"TSModuleBlock:exit": popConfigScope,
|
|
304
|
+
SwitchStatement: pushConfigScope,
|
|
305
|
+
"SwitchStatement:exit": popConfigScope,
|
|
306
|
+
CatchClause: handleCatchEnter,
|
|
307
|
+
"CatchClause:exit": popConfigScope,
|
|
308
|
+
VariableDeclarator(node) {
|
|
309
|
+
const names = /* @__PURE__ */ new Set();
|
|
310
|
+
extractIdentifiers(node.id, names);
|
|
311
|
+
for (const name of names) if (configFns.has(name)) shadowedConfigScopes[shadowedConfigScopes.length - 1].add(name);
|
|
312
|
+
},
|
|
313
|
+
CallExpression(node) {
|
|
314
|
+
if (configFns.size === 0) return;
|
|
315
|
+
const { callee } = node;
|
|
316
|
+
const calleeName = callee.type === "Identifier" ? callee.name : null;
|
|
317
|
+
if (!calleeName || !configFns.has(calleeName)) return;
|
|
318
|
+
if (isConfigShadowed(calleeName)) return;
|
|
319
|
+
const configArg = require_ast.unwrapTypeAssertion(node.arguments?.[0]);
|
|
320
|
+
if (require_ast.configNeedsMetaSink(configArg)) ctx.report({
|
|
321
|
+
node,
|
|
322
|
+
message: "Add a dedicated sink for the meta logger (category: [\"logtape\"] or [\"logtape\", \"meta\"]) to handle LogTape's own diagnostic messages."
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
} }
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
var plugin_default = logtapePlugin;
|
|
330
|
+
|
|
331
|
+
//#endregion
|
|
332
|
+
module.exports = plugin_default;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
//#region src/deno/plugin.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Deno Lint plugin for LogTape lint rules.
|
|
4
|
+
*
|
|
5
|
+
* > [!NOTE]
|
|
6
|
+
* > The Deno Lint plugin API is currently experimental (unstable).
|
|
7
|
+
* > This plugin requires Deno 2.2.0 or later with the `--unstable-lint` flag
|
|
8
|
+
* > or the `"unstable": ["lint"]` option in your `deno.json`.
|
|
9
|
+
*
|
|
10
|
+
* Add this plugin to your `deno.json`:
|
|
11
|
+
* ```json
|
|
12
|
+
* {
|
|
13
|
+
* "lint": {
|
|
14
|
+
* "plugins": ["jsr:@logtape/lint/deno"],
|
|
15
|
+
* "rules": {
|
|
16
|
+
* "include": [
|
|
17
|
+
* "logtape/no-message-interpolation",
|
|
18
|
+
* "logtape/prefer-lazy-evaluation",
|
|
19
|
+
* "logtape/no-unawaited-log",
|
|
20
|
+
* "logtape/require-meta-sink"
|
|
21
|
+
* ]
|
|
22
|
+
* }
|
|
23
|
+
* }
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* The rule detection logic is shared with the ESLint rules via `../core/ast.ts`;
|
|
28
|
+
* only the scope tracking (which the Deno Lint API does not provide a manager
|
|
29
|
+
* for) and the report/fix wiring are implemented here.
|
|
30
|
+
*
|
|
31
|
+
* @module
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* Deno Lint plugin providing LogTape lint rules.
|
|
35
|
+
*
|
|
36
|
+
* > [!WARNING]
|
|
37
|
+
* > The Deno Lint plugin API is experimental. This plugin may break between
|
|
38
|
+
* > Deno releases while the API is stabilised.
|
|
39
|
+
*/
|
|
40
|
+
declare const logtapePlugin: {
|
|
41
|
+
name: string;
|
|
42
|
+
rules: Record<string, unknown>;
|
|
43
|
+
};
|
|
44
|
+
//#endregion
|
|
45
|
+
export { logtapePlugin as default };
|
|
46
|
+
//# sourceMappingURL=plugin.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.cts","names":[],"sources":["../../src/deno/plugin.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA4YM;;SAAsC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
//#region src/deno/plugin.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Deno Lint plugin for LogTape lint rules.
|
|
4
|
+
*
|
|
5
|
+
* > [!NOTE]
|
|
6
|
+
* > The Deno Lint plugin API is currently experimental (unstable).
|
|
7
|
+
* > This plugin requires Deno 2.2.0 or later with the `--unstable-lint` flag
|
|
8
|
+
* > or the `"unstable": ["lint"]` option in your `deno.json`.
|
|
9
|
+
*
|
|
10
|
+
* Add this plugin to your `deno.json`:
|
|
11
|
+
* ```json
|
|
12
|
+
* {
|
|
13
|
+
* "lint": {
|
|
14
|
+
* "plugins": ["jsr:@logtape/lint/deno"],
|
|
15
|
+
* "rules": {
|
|
16
|
+
* "include": [
|
|
17
|
+
* "logtape/no-message-interpolation",
|
|
18
|
+
* "logtape/prefer-lazy-evaluation",
|
|
19
|
+
* "logtape/no-unawaited-log",
|
|
20
|
+
* "logtape/require-meta-sink"
|
|
21
|
+
* ]
|
|
22
|
+
* }
|
|
23
|
+
* }
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* The rule detection logic is shared with the ESLint rules via `../core/ast.ts`;
|
|
28
|
+
* only the scope tracking (which the Deno Lint API does not provide a manager
|
|
29
|
+
* for) and the report/fix wiring are implemented here.
|
|
30
|
+
*
|
|
31
|
+
* @module
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* Deno Lint plugin providing LogTape lint rules.
|
|
35
|
+
*
|
|
36
|
+
* > [!WARNING]
|
|
37
|
+
* > The Deno Lint plugin API is experimental. This plugin may break between
|
|
38
|
+
* > Deno releases while the API is stabilised.
|
|
39
|
+
*/
|
|
40
|
+
declare const logtapePlugin: {
|
|
41
|
+
name: string;
|
|
42
|
+
rules: Record<string, unknown>;
|
|
43
|
+
};
|
|
44
|
+
//#endregion
|
|
45
|
+
export { logtapePlugin as default };
|
|
46
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","names":[],"sources":["../../src/deno/plugin.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA4YM;;SAAsC"}
|