@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,333 @@
|
|
|
1
|
+
import { LOG_METHODS, canInsertAwait, configNeedsMetaSink, containsAwaitOrYield, isAsyncFunctionExpr, isLogPromiseHandled, isLogtapeImportSource, isPromiseReturningCallback, logMethodName, propsHaveEagerCall, selectLazyPropsObject, unwrapTypeAssertion } from "../core/ast.js";
|
|
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 || 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 || 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 (!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, isAsyncFunctionExpr(init) || 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 = logMethodName(node.callee);
|
|
193
|
+
if (!methodName || !LOG_METHODS.has(methodName)) return;
|
|
194
|
+
const firstArg = 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 = logMethodName(node.callee);
|
|
211
|
+
if (!methodName || !LOG_METHODS.has(methodName)) return;
|
|
212
|
+
const selected = selectLazyPropsObject(node.arguments);
|
|
213
|
+
if (!selected) return;
|
|
214
|
+
const { propsObject, fixTarget, propertiesOnly } = selected;
|
|
215
|
+
if (!propsHaveEagerCall(propsObject, scope.effectiveLazyNames())) return;
|
|
216
|
+
const hasAsyncSyntax = 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 = logMethodName(node.callee);
|
|
237
|
+
if (!methodName || !LOG_METHODS.has(methodName)) return;
|
|
238
|
+
const secondArg = unwrapTypeAssertion(node.arguments?.[1]);
|
|
239
|
+
if (!secondArg) return;
|
|
240
|
+
const isAsyncCallback = isAsyncFunctionExpr(secondArg) || isPromiseReturningCallback(secondArg) || secondArg.type === "Identifier" && scope.isAsyncFunctionName(secondArg.name);
|
|
241
|
+
if (!isAsyncCallback) return;
|
|
242
|
+
if (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: 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 (!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 = unwrapTypeAssertion(node.arguments?.[0]);
|
|
320
|
+
if (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
|
+
export { plugin_default as default };
|
|
333
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","names":["node: any","names: Set<string>","scopeStack: Array<Map<string, boolean>>","shadowedGetterStack: Array<Set<string>>","shadowedLazyStack: Array<Set<string>>","asyncFnStack: Array<Map<string, boolean>>","name: string","callee: any","logtapePlugin: { name: string; rules: Record<string, unknown> }","ctx: any","fixer: any","shadowedConfigScopes: Array<Set<string>>"],"sources":["../../src/deno/plugin.ts"],"sourcesContent":["/**\n * Deno Lint plugin for LogTape lint rules.\n *\n * > [!NOTE]\n * > The Deno Lint plugin API is currently experimental (unstable).\n * > This plugin requires Deno 2.2.0 or later with the `--unstable-lint` flag\n * > or the `\"unstable\": [\"lint\"]` option in your `deno.json`.\n *\n * Add this plugin to your `deno.json`:\n * ```json\n * {\n * \"lint\": {\n * \"plugins\": [\"jsr:@logtape/lint/deno\"],\n * \"rules\": {\n * \"include\": [\n * \"logtape/no-message-interpolation\",\n * \"logtape/prefer-lazy-evaluation\",\n * \"logtape/no-unawaited-log\",\n * \"logtape/require-meta-sink\"\n * ]\n * }\n * }\n * }\n * ```\n *\n * The rule detection logic is shared with the ESLint rules via `../core/ast.ts`;\n * only the scope tracking (which the Deno Lint API does not provide a manager\n * for) and the report/fix wiring are implemented here.\n *\n * @module\n */\n\n// deno-lint-ignore-file no-explicit-any\n\nimport {\n canInsertAwait,\n configNeedsMetaSink,\n containsAwaitOrYield,\n isAsyncFunctionExpr,\n isLogPromiseHandled,\n isLogtapeImportSource,\n isPromiseReturningCallback,\n LOG_METHODS,\n logMethodName,\n propsHaveEagerCall,\n selectLazyPropsObject,\n unwrapTypeAssertion,\n} from \"../core/ast.ts\";\n\n// Collect every identifier name bound by a (possibly destructured) binding\n// target: a plain parameter, an object/array pattern, a default, or a rest.\nfunction extractIdentifiers(node: any, names: Set<string>): void {\n if (!node) return;\n if (node.type === \"Identifier\") {\n names.add(node.name);\n } else if (node.type === \"ObjectPattern\") {\n for (const prop of node.properties ?? []) {\n if (prop.type === \"Property\") {\n extractIdentifiers(prop.value, names);\n } else if (prop.type === \"RestElement\") {\n extractIdentifiers(prop.argument, names);\n }\n }\n } else if (node.type === \"ArrayPattern\") {\n for (const elem of node.elements ?? []) {\n extractIdentifiers(elem, names);\n }\n } else if (node.type === \"AssignmentPattern\") {\n extractIdentifiers(node.left, names);\n } else if (node.type === \"RestElement\") {\n extractIdentifiers(node.argument, names);\n } else if (node.type === \"TSParameterProperty\") {\n // A constructor parameter property (`constructor(private logger: Logger)`)\n // wraps the actual binding under `.parameter`.\n extractIdentifiers(node.parameter, names);\n }\n}\n\n/**\n * Creates a scope tracker for LogTape logger variable bindings.\n *\n * The Deno Lint plugin API does not expose a scope manager, so this hand-rolls\n * one with a scope stack (Map<name, isLogger>) to correctly handle:\n * - Function parameters that shadow logger names (tracked as false)\n * - Local variables that re-declare logger names with non-logger values\n * - Block scoping via BlockStatement enter/exit\n *\n * Returns a `visitors` object to spread into a rule's visitor map, plus the\n * scope-aware predicates the rule's `CallExpression` handler needs.\n */\nfunction makeLoggerScope(): {\n visitors: Record<string, (node: any) => void>;\n isLogtapeCallee: (callee: any) => boolean;\n isAsyncFunctionName: (name: string) => boolean;\n lazyNames: Set<string>;\n effectiveLazyNames: () => Set<string>;\n} {\n const getterNames = new Set<string>();\n // Local names of `lazy` imported from @logtape/logtape; a lazy() value is\n // already deferred and must not be treated as an eager call.\n const lazyNames = new Set<string>();\n // Each Map entry: true = logger binding, false = shadowing non-logger\n const scopeStack: Array<Map<string, boolean>> = [new Map()];\n // Tracks scopes where a getter name is shadowed by a parameter or local\n const shadowedGetterStack: Array<Set<string>> = [new Set()];\n // Tracks scopes where an imported `lazy` name is shadowed by a local binding\n const shadowedLazyStack: Array<Set<string>> = [new Set()];\n // Per-scope promise-callback bindings: true = bound to a function that yields\n // a promise (async, or non-async but syntactically promise-returning), false\n // = bound to something else (a parameter, plain sync local, etc.) that\n // shadows an outer binding. Lookups stop at the nearest binding, so an outer\n // name does not leak into an inner scope that rebinds it.\n const asyncFnStack: Array<Map<string, boolean>> = [new Map()];\n\n function pushScope() {\n scopeStack.push(new Map());\n shadowedGetterStack.push(new Set());\n shadowedLazyStack.push(new Set());\n asyncFnStack.push(new Map());\n }\n\n function popScope() {\n if (scopeStack.length > 1) scopeStack.pop();\n if (shadowedGetterStack.length > 1) shadowedGetterStack.pop();\n if (shadowedLazyStack.length > 1) shadowedLazyStack.pop();\n if (asyncFnStack.length > 1) asyncFnStack.pop();\n }\n\n function isLoggerName(name: string): boolean {\n for (let i = scopeStack.length - 1; i >= 0; i--) {\n if (scopeStack[i].has(name)) return scopeStack[i].get(name)!;\n }\n return false;\n }\n\n // Whether `name` resolves to a binding that yields a promise (an async\n // function or a non-async promise-returning one) in the current scope chain.\n function isAsyncFunctionName(name: string): boolean {\n for (let i = asyncFnStack.length - 1; i >= 0; i--) {\n if (asyncFnStack[i].has(name)) return asyncFnStack[i].get(name)!;\n }\n return false;\n }\n\n function isGetterShadowed(name: string): boolean {\n for (let i = shadowedGetterStack.length - 1; i >= 0; i--) {\n if (shadowedGetterStack[i].has(name)) return true;\n }\n return false;\n }\n\n function isLazyShadowed(name: string): boolean {\n for (let i = shadowedLazyStack.length - 1; i >= 0; i--) {\n if (shadowedLazyStack[i].has(name)) return true;\n }\n return false;\n }\n\n // Record that `name`, a local binding in the current scope, shadows the\n // imported getLogger and/or lazy. Names are recorded unconditionally rather\n // than gated on getterNames/lazyNames: this also runs during the Program\n // pre-scan, before the ImportDeclaration visitor has populated those sets, so\n // gating would miss a top-level shadow. Recording extra names is harmless,\n // since the stacks are only ever queried for the imported getLogger/lazy\n // names.\n function recordImportShadow(name: string) {\n shadowedGetterStack[shadowedGetterStack.length - 1].add(name);\n shadowedLazyStack[shadowedLazyStack.length - 1].add(name);\n }\n\n // The subset of lazyNames not shadowed by a local binding in the current\n // scope chain, so a shadowed `lazy(...)` is treated as an ordinary eager\n // call rather than LogTape's deferred wrapper.\n function effectiveLazyNames(): Set<string> {\n if (lazyNames.size === 0) return lazyNames;\n const result = new Set<string>();\n for (const name of lazyNames) {\n if (!isLazyShadowed(name)) result.add(name);\n }\n return result;\n }\n\n function handleFunctionEnter(node: any) {\n // A named function declaration binds its name in the ENCLOSING scope, so\n // record getter shadowing and promise-returning-ness there before pushing\n // the new scope: `async function props() {}`, or a non-async\n // `function props() { return p.then(...) }`, makes `props` a promise\n // callback.\n if (\n node.type === \"FunctionDeclaration\" &&\n node.id?.type === \"Identifier\"\n ) {\n recordImportShadow(node.id.name);\n // A function declaration named like a logger shadows it: it is a\n // function, never a LogTape logger.\n scopeStack[scopeStack.length - 1].set(node.id.name, false);\n asyncFnStack[asyncFnStack.length - 1].set(\n node.id.name,\n node.async === true || isPromiseReturningCallback(node),\n );\n }\n pushScope();\n const names = new Set<string>();\n // A named function expression binds its own name inside its body, shadowing\n // any outer binding of that name (a logger, getLogger, etc.).\n if (node.type === \"FunctionExpression\" && node.id?.type === \"Identifier\") {\n names.add(node.id.name);\n }\n for (const param of node.params ?? []) {\n extractIdentifiers(param, names);\n }\n for (const name of names) {\n scopeStack[scopeStack.length - 1].set(name, false);\n recordImportShadow(name);\n // A parameter rebinds the name, shadowing any outer async-callback\n // binding so it does not leak into the function body.\n asyncFnStack[asyncFnStack.length - 1].set(name, false);\n }\n }\n\n // Function declarations are hoisted to the top of their block, so a local\n // `function getLogger() {}` shadows the import for the whole block, including\n // statements that appear before it. Pre-scan the block's direct statements\n // on entry and record those shadows before any statement is visited.\n function handleBlockEnter(node: any) {\n pushScope();\n for (const stmt of node.body ?? []) {\n if (\n stmt?.type === \"FunctionDeclaration\" && stmt.id?.type === \"Identifier\"\n ) {\n recordImportShadow(stmt.id.name);\n // A hoisted function declaration named like a logger shadows it for the\n // whole block, including earlier statements; it is never a logger.\n scopeStack[scopeStack.length - 1].set(stmt.id.name, false);\n // Hoist promise-returning-ness too, so a callback referencing a\n // function declared later in the block is still recognized.\n asyncFnStack[asyncFnStack.length - 1].set(\n stmt.id.name,\n stmt.async === true || isPromiseReturningCallback(stmt),\n );\n }\n }\n }\n\n // A catch parameter binds names in the catch scope, the same as function\n // parameters; record them as non-logger bindings so a `catch (logger) {}`\n // does not resolve to an outer LogTape logger.\n function handleCatchEnter(node: any) {\n pushScope();\n if (!node.param) return;\n const names = new Set<string>();\n extractIdentifiers(node.param, names);\n for (const name of names) {\n scopeStack[scopeStack.length - 1].set(name, false);\n recordImportShadow(name);\n // A catch parameter rebinds the name, shadowing any outer async binding.\n asyncFnStack[asyncFnStack.length - 1].set(name, false);\n }\n }\n\n // Is `node` an expression that evaluates to a LogTape logger? Handles\n // direct getLogger(...) calls (unless the getter name is shadowed),\n // identifiers already bound to a logger in scope, and contextual or child\n // loggers produced by chaining Logger.with(...) or Logger.getChild(...).\n function isLoggerExpr(node: any, depth = 0): boolean {\n if (depth > 16 || !node) return false;\n // getLogger(...) — direct call to the (unshadowed) imported getter.\n if (\n node.type === \"CallExpression\" &&\n node.callee?.type === \"Identifier\" &&\n getterNames.has(node.callee.name) &&\n !isGetterShadowed(node.callee.name)\n ) return true;\n // logger.with(...) / logger.getChild(...) — these return a Logger, so the\n // whole call expression is itself a logger expression.\n if (\n node.type === \"CallExpression\" &&\n node.callee?.type === \"MemberExpression\" &&\n !node.callee.computed &&\n node.callee.property?.type === \"Identifier\" &&\n (node.callee.property.name === \"with\" ||\n node.callee.property.name === \"getChild\")\n ) {\n return isLoggerExpr(node.callee.object, depth + 1);\n }\n // An identifier already bound to a logger in the current scope chain.\n if (node.type === \"Identifier\") return isLoggerName(node.name);\n return false;\n }\n\n function isLogtapeCallee(callee: any): boolean {\n if (!callee || callee.type !== \"MemberExpression\") return false;\n // The object may be a logger identifier, an inline getLogger(...) call, or\n // a contextual/child logger chain such as getLogger(\"app\").with({ ... }).\n return isLoggerExpr(callee.object);\n }\n\n return {\n visitors: {\n ImportDeclaration(node: any) {\n if (!isLogtapeImportSource(node.source?.value)) return;\n for (const spec of node.specifiers ?? []) {\n if (spec.type !== \"ImportSpecifier\") continue;\n if (spec.imported?.name === \"getLogger\") {\n getterNames.add(spec.local?.name);\n } else if (spec.imported?.name === \"lazy\") {\n lazyNames.add(spec.local?.name);\n }\n }\n },\n VariableDeclarator(node: any) {\n if (node.id?.type === \"Identifier\") {\n const name = node.id.name;\n const init = node.init;\n const isLogger = isLoggerExpr(init);\n if (isLogger) {\n scopeStack[scopeStack.length - 1].set(name, true);\n } else if (isLoggerName(name)) {\n // Explicit non-logger assignment shadows the outer logger binding\n scopeStack[scopeStack.length - 1].set(name, false);\n }\n // A local declaration shadows an imported getLogger or lazy, so a\n // later inline getLogger()/lazy() in this scope is the local binding,\n // not the import.\n recordImportShadow(name);\n // Record whether this local is bound to a function literal that\n // yields a promise (async, or non-async but promise-returning) so a\n // lazy callback passed by reference is detected; any other binding\n // shadows an outer promise-callback binding of the same name.\n asyncFnStack[asyncFnStack.length - 1].set(\n name,\n isAsyncFunctionExpr(init) || isPromiseReturningCallback(init),\n );\n } else {\n // Destructured declaration (e.g. const { logger } = obj): shadow any\n // matching names to avoid false positives.\n const names = new Set<string>();\n extractIdentifiers(node.id, names);\n for (const name of names) {\n if (isLoggerName(name)) {\n scopeStack[scopeStack.length - 1].set(name, false);\n }\n // A destructured getLogger/lazy name is likewise a local,\n // non-import binding.\n recordImportShadow(name);\n // A destructured binding is not a known async function and shadows\n // any outer async binding of the same name.\n asyncFnStack[asyncFnStack.length - 1].set(name, false);\n }\n }\n },\n FunctionDeclaration: handleFunctionEnter,\n \"FunctionDeclaration:exit\": popScope,\n FunctionExpression: handleFunctionEnter,\n \"FunctionExpression:exit\": popScope,\n ArrowFunctionExpression: handleFunctionEnter,\n \"ArrowFunctionExpression:exit\": popScope,\n // Program is a block-like scope: pre-scan it too so a top-level function\n // declared below a callback that references it is still hoisted.\n Program: handleBlockEnter,\n \"Program:exit\": popScope,\n BlockStatement: handleBlockEnter,\n \"BlockStatement:exit\": popScope,\n // A TypeScript namespace body (TSModuleBlock) is a lexical scope whose\n // statements are not wrapped in a block statement, so track it like a\n // block. A class static block needs no separate handling: its body is\n // itself a BlockStatement, already covered above.\n TSModuleBlock: handleBlockEnter,\n \"TSModuleBlock:exit\": popScope,\n ForStatement: pushScope,\n \"ForStatement:exit\": popScope,\n ForInStatement: pushScope,\n \"ForInStatement:exit\": popScope,\n ForOfStatement: pushScope,\n \"ForOfStatement:exit\": popScope,\n // A switch block is a single lexical scope for its let/const\n // declarations, so push/pop one for it like a block.\n SwitchStatement: pushScope,\n \"SwitchStatement:exit\": popScope,\n CatchClause: handleCatchEnter,\n \"CatchClause:exit\": popScope,\n },\n isLogtapeCallee,\n isAsyncFunctionName,\n lazyNames,\n effectiveLazyNames,\n };\n}\n\n/**\n * Deno Lint plugin providing LogTape lint rules.\n *\n * > [!WARNING]\n * > The Deno Lint plugin API is experimental. This plugin may break between\n * > Deno releases while the API is stabilised.\n */\nconst logtapePlugin: { name: string; rules: Record<string, unknown> } = {\n name: \"logtape\",\n\n rules: {\n /** Disallow template literal interpolation in log message arguments. */\n \"no-message-interpolation\": {\n create(ctx: any) {\n const scope = makeLoggerScope();\n return {\n ...scope.visitors,\n CallExpression(node: any) {\n if (!scope.isLogtapeCallee(node.callee)) return;\n const methodName = logMethodName(node.callee);\n if (!methodName || !LOG_METHODS.has(methodName)) return;\n const firstArg = unwrapTypeAssertion(node.arguments?.[0]);\n if (!firstArg || firstArg.type !== \"TemplateLiteral\") return;\n if (!firstArg.expressions?.length) return;\n ctx.report({\n node: firstArg,\n message:\n \"Avoid using template literal interpolation in log messages. \" +\n 'Use a message template string with structured properties instead: logger.info(\"User {userId} logged in.\", { userId }).',\n });\n },\n };\n },\n },\n\n /** Prefer lazy evaluation callbacks over eager property objects. */\n \"prefer-lazy-evaluation\": {\n create(ctx: any) {\n const scope = makeLoggerScope();\n return {\n ...scope.visitors,\n CallExpression(node: any) {\n if (!scope.isLogtapeCallee(node.callee)) return;\n const methodName = logMethodName(node.callee);\n if (!methodName || !LOG_METHODS.has(methodName)) return;\n // The eager properties object is the second argument in the\n // message+properties form, logger.debug(\"msg\", { ... }), or the\n // first argument in the properties-only form, logger.debug({ ... }).\n const selected = selectLazyPropsObject(node.arguments);\n if (!selected) return;\n const { propsObject, fixTarget, propertiesOnly } = selected;\n // Use the lazy names that still resolve to the import in this\n // scope, so a shadowed local `lazy(...)` is treated as eager.\n if (!propsHaveEagerCall(propsObject, scope.effectiveLazyNames())) {\n return;\n }\n const hasAsyncSyntax = containsAwaitOrYield(propsObject);\n ctx.report({\n node: propsObject,\n message:\n \"Wrap the properties object in a lazy callback to avoid unnecessary computation: \" +\n 'logger.debug(\"msg\", () => ({ ... })).',\n // Wrap the whole argument (fixTarget), including any `as const` /\n // `satisfies` wrapper, so the assertion ends up inside the\n // callback instead of dangling on it.\n fix: hasAsyncSyntax ? undefined : (fixer: any) => {\n const sourceCode = ctx.sourceCode ?? ctx.getSourceCode?.();\n // Only slice by range when the source is a raw string and the\n // node actually carries a range; otherwise fall back to\n // getText so a missing range cannot throw.\n const text = typeof sourceCode === \"string\" && fixTarget.range\n ? sourceCode.slice(fixTarget.range[0], fixTarget.range[1])\n : sourceCode?.getText?.(fixTarget);\n if (!text) return null;\n // The properties-only overload needs the \"{*}\" message inserted\n // so the lazy callback is still read as properties.\n return fixer.replaceText(\n fixTarget,\n propertiesOnly ? `\"{*}\", () => (${text})` : `() => (${text})`,\n );\n },\n });\n },\n };\n },\n },\n\n /** Require await on log calls that use async lazy callbacks. */\n \"no-unawaited-log\": {\n create(ctx: any) {\n const scope = makeLoggerScope();\n return {\n ...scope.visitors,\n CallExpression(node: any) {\n if (!scope.isLogtapeCallee(node.callee)) return;\n const methodName = logMethodName(node.callee);\n if (!methodName || !LOG_METHODS.has(methodName)) return;\n const secondArg = unwrapTypeAssertion(node.arguments?.[1]);\n if (!secondArg) return;\n const isAsyncCallback = isAsyncFunctionExpr(secondArg) ||\n isPromiseReturningCallback(secondArg) ||\n (secondArg.type === \"Identifier\" &&\n scope.isAsyncFunctionName(secondArg.name));\n if (!isAsyncCallback) return;\n // Walk the ancestor chain to check if the promise is handled\n // (awaited, returned, chained with .then(), or in Promise.all()).\n if (isLogPromiseHandled(node)) return;\n ctx.report({\n node,\n message:\n \"Async lazy callbacks must be awaited to ensure the log is flushed: \" +\n 'await logger.debug(\"msg\", async () => ({ ... })).',\n // Only autofix a standalone statement. Inserting `await` where\n // the call's value is used would change a Promise<void> into void\n // and can break code that uses that promise.\n fix: canInsertAwait(node)\n ? (fixer: any) => fixer.insertTextBefore(node, \"await \")\n : undefined,\n });\n },\n };\n },\n },\n\n /** Require a meta sink in configure() / configureSync() calls. */\n \"require-meta-sink\": {\n create(ctx: any) {\n const configFns = new Set<string>();\n // Tracks scopes where a configure/configureSync name is shadowed\n const shadowedConfigScopes: Array<Set<string>> = [new Set()];\n\n function pushConfigScope() {\n shadowedConfigScopes.push(new Set());\n }\n\n function popConfigScope() {\n if (shadowedConfigScopes.length > 1) shadowedConfigScopes.pop();\n }\n\n function isConfigShadowed(name: string): boolean {\n for (let i = shadowedConfigScopes.length - 1; i >= 0; i--) {\n if (shadowedConfigScopes[i].has(name)) return true;\n }\n return false;\n }\n\n function handleFnEnter(node: any) {\n // A named function declaration binds its name in the ENCLOSING scope,\n // so a local `function configure()` shadows the import; record that\n // before pushing the new scope.\n if (\n node.type === \"FunctionDeclaration\" &&\n node.id?.type === \"Identifier\" &&\n configFns.has(node.id.name)\n ) {\n shadowedConfigScopes[shadowedConfigScopes.length - 1].add(\n node.id.name,\n );\n }\n pushConfigScope();\n const names = new Set<string>();\n // A named function expression binds its own name inside its body,\n // shadowing an imported configure/configureSync of the same name.\n if (\n node.type === \"FunctionExpression\" && node.id?.type === \"Identifier\"\n ) {\n names.add(node.id.name);\n }\n for (const param of node.params ?? []) {\n extractIdentifiers(param, names);\n }\n for (const name of names) {\n if (configFns.has(name)) {\n shadowedConfigScopes[shadowedConfigScopes.length - 1].add(name);\n }\n }\n }\n\n // Function declarations are hoisted to the top of their block, so a\n // local `function configure() {}` shadows the import for the whole\n // block, including earlier statements. Pre-scan the block's direct\n // statements on entry and record those shadows first.\n //\n // The name is recorded unconditionally rather than gated on configFns:\n // this handler also runs for the Program node, which is entered before\n // the top-level ImportDeclaration populates configFns, so gating would\n // miss a top-level shadow. Recording extra names is harmless because\n // isConfigShadowed is only consulted for names already known to be\n // imported configure/configureSync, and each name is scoped to its\n // block via push/popConfigScope.\n function handleBlockEnter(node: any) {\n pushConfigScope();\n for (const stmt of node.body ?? []) {\n if (\n stmt?.type === \"FunctionDeclaration\" &&\n stmt.id?.type === \"Identifier\"\n ) {\n shadowedConfigScopes[shadowedConfigScopes.length - 1].add(\n stmt.id.name,\n );\n }\n }\n }\n\n // A catch parameter named like an imported configure function shadows\n // it within the catch scope, so record it before visiting the body.\n function handleCatchEnter(node: any) {\n pushConfigScope();\n if (!node.param) return;\n const names = new Set<string>();\n extractIdentifiers(node.param, names);\n for (const name of names) {\n if (configFns.has(name)) {\n shadowedConfigScopes[shadowedConfigScopes.length - 1].add(name);\n }\n }\n }\n\n return {\n ImportDeclaration(node: any) {\n if (!isLogtapeImportSource(node.source?.value)) return;\n for (const specifier of node.specifiers ?? []) {\n if (specifier.type !== \"ImportSpecifier\") continue;\n const imported = specifier.imported?.name;\n if (\n imported === \"configure\" || imported === \"configureSync\"\n ) {\n configFns.add(specifier.local?.name);\n }\n }\n },\n FunctionDeclaration: handleFnEnter,\n \"FunctionDeclaration:exit\": popConfigScope,\n FunctionExpression: handleFnEnter,\n \"FunctionExpression:exit\": popConfigScope,\n ArrowFunctionExpression: handleFnEnter,\n \"ArrowFunctionExpression:exit\": popConfigScope,\n // Program is pre-scanned too so a top-level `function configure()`\n // declared below its call still shadows the import.\n Program: handleBlockEnter,\n \"Program:exit\": popConfigScope,\n BlockStatement: handleBlockEnter,\n \"BlockStatement:exit\": popConfigScope,\n // A TypeScript namespace body (TSModuleBlock) is a lexical scope\n // whose statements are not wrapped in a block statement. A class\n // static block needs no separate handling: its body is itself a\n // BlockStatement, already covered above.\n TSModuleBlock: handleBlockEnter,\n \"TSModuleBlock:exit\": popConfigScope,\n // A switch block is a single lexical scope for its let/const\n // declarations, so push/pop one for it like a block.\n SwitchStatement: pushConfigScope,\n \"SwitchStatement:exit\": popConfigScope,\n CatchClause: handleCatchEnter,\n \"CatchClause:exit\": popConfigScope,\n VariableDeclarator(node: any) {\n // A local declaration shadows any imported configure/configureSync\n // with the same name, so we must not flag it as a LogTape call.\n const names = new Set<string>();\n extractIdentifiers(node.id, names);\n for (const name of names) {\n if (configFns.has(name)) {\n shadowedConfigScopes[shadowedConfigScopes.length - 1].add(name);\n }\n }\n },\n CallExpression(node: any) {\n if (configFns.size === 0) return;\n const { callee } = node;\n const calleeName = callee.type === \"Identifier\"\n ? callee.name\n : null;\n if (!calleeName || !configFns.has(calleeName)) return;\n if (isConfigShadowed(calleeName)) return;\n // Unwrap a TypeScript type assertion (e.g. configure({ ... } as T)).\n const configArg = unwrapTypeAssertion(node.arguments?.[0]);\n if (configNeedsMetaSink(configArg)) {\n ctx.report({\n node,\n message:\n 'Add a dedicated sink for the meta logger (category: [\"logtape\"] or [\"logtape\", \"meta\"]) ' +\n \"to handle LogTape's own diagnostic messages.\",\n });\n }\n },\n };\n },\n },\n },\n};\n\nexport default logtapePlugin;\n"],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
|
+
const require_no_message_interpolation = require('../rules/no-message-interpolation.cjs');
|
|
3
|
+
const require_no_unawaited_log = require('../rules/no-unawaited-log.cjs');
|
|
4
|
+
const require_prefer_lazy_evaluation = require('../rules/prefer-lazy-evaluation.cjs');
|
|
5
|
+
const require_require_meta_sink = require('../rules/require-meta-sink.cjs');
|
|
6
|
+
|
|
7
|
+
//#region src/eslint/plugin.ts
|
|
8
|
+
/**
|
|
9
|
+
* All LogTape lint rules.
|
|
10
|
+
*/
|
|
11
|
+
const rules = {
|
|
12
|
+
"no-message-interpolation": require_no_message_interpolation.noMessageInterpolation,
|
|
13
|
+
"prefer-lazy-evaluation": require_prefer_lazy_evaluation.preferLazyEvaluation,
|
|
14
|
+
"no-unawaited-log": require_no_unawaited_log.noUnawaitedLog,
|
|
15
|
+
"require-meta-sink": require_require_meta_sink.requireMetaSink
|
|
16
|
+
};
|
|
17
|
+
const plugin = {
|
|
18
|
+
meta: { name: "@logtape/lint" },
|
|
19
|
+
rules,
|
|
20
|
+
configs: {}
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Recommended ESLint v9 flat config preset.
|
|
24
|
+
*
|
|
25
|
+
* Enables all LogTape rules with their recommended severity levels:
|
|
26
|
+
* - `no-message-interpolation`: error
|
|
27
|
+
* - `prefer-lazy-evaluation`: warn
|
|
28
|
+
* - `no-unawaited-log`: error
|
|
29
|
+
* - `require-meta-sink`: warn
|
|
30
|
+
*/
|
|
31
|
+
const recommended = {
|
|
32
|
+
plugins: { logtape: plugin },
|
|
33
|
+
rules: {
|
|
34
|
+
"logtape/no-message-interpolation": "error",
|
|
35
|
+
"logtape/prefer-lazy-evaluation": "warn",
|
|
36
|
+
"logtape/no-unawaited-log": "error",
|
|
37
|
+
"logtape/require-meta-sink": "warn"
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
plugin.configs.recommended = recommended;
|
|
41
|
+
var plugin_default = plugin;
|
|
42
|
+
|
|
43
|
+
//#endregion
|
|
44
|
+
exports.default = plugin_default;
|
|
45
|
+
exports.noMessageInterpolation = require_no_message_interpolation.noMessageInterpolation;
|
|
46
|
+
exports.noUnawaitedLog = require_no_unawaited_log.noUnawaitedLog;
|
|
47
|
+
exports.plugin = plugin;
|
|
48
|
+
exports.preferLazyEvaluation = require_prefer_lazy_evaluation.preferLazyEvaluation;
|
|
49
|
+
exports.recommended = recommended;
|
|
50
|
+
exports.requireMetaSink = require_require_meta_sink.requireMetaSink;
|
|
51
|
+
exports.rules = rules;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { noMessageInterpolation } from "../rules/no-message-interpolation.cjs";
|
|
2
|
+
import { noUnawaitedLog } from "../rules/no-unawaited-log.cjs";
|
|
3
|
+
import { preferLazyEvaluation } from "../rules/prefer-lazy-evaluation.cjs";
|
|
4
|
+
import { requireMetaSink } from "../rules/require-meta-sink.cjs";
|
|
5
|
+
import { Linter, Rule } from "eslint";
|
|
6
|
+
|
|
7
|
+
//#region src/eslint/plugin.d.ts
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* All LogTape lint rules.
|
|
11
|
+
*/
|
|
12
|
+
declare const rules: Record<string, Rule.RuleModule>;
|
|
13
|
+
declare const plugin: any;
|
|
14
|
+
/**
|
|
15
|
+
* Recommended ESLint v9 flat config preset.
|
|
16
|
+
*
|
|
17
|
+
* Enables all LogTape rules with their recommended severity levels:
|
|
18
|
+
* - `no-message-interpolation`: error
|
|
19
|
+
* - `prefer-lazy-evaluation`: warn
|
|
20
|
+
* - `no-unawaited-log`: error
|
|
21
|
+
* - `require-meta-sink`: warn
|
|
22
|
+
*/
|
|
23
|
+
declare const recommended: Linter.Config;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { plugin as default, plugin, noMessageInterpolation, noUnawaitedLog, preferLazyEvaluation, recommended, requireMetaSink, rules };
|
|
26
|
+
//# sourceMappingURL=plugin.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.cts","names":[],"sources":["../../src/eslint/plugin.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;cA6Ca,OAAO,eAAe,IAAA,CAAK;cAS3B;;;;;;;;;;cAeA,aAAa,MAAA,CAAO"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { noMessageInterpolation } from "../rules/no-message-interpolation.js";
|
|
2
|
+
import { noUnawaitedLog } from "../rules/no-unawaited-log.js";
|
|
3
|
+
import { preferLazyEvaluation } from "../rules/prefer-lazy-evaluation.js";
|
|
4
|
+
import { requireMetaSink } from "../rules/require-meta-sink.js";
|
|
5
|
+
import { Linter, Rule } from "eslint";
|
|
6
|
+
|
|
7
|
+
//#region src/eslint/plugin.d.ts
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* All LogTape lint rules.
|
|
11
|
+
*/
|
|
12
|
+
declare const rules: Record<string, Rule.RuleModule>;
|
|
13
|
+
declare const plugin: any;
|
|
14
|
+
/**
|
|
15
|
+
* Recommended ESLint v9 flat config preset.
|
|
16
|
+
*
|
|
17
|
+
* Enables all LogTape rules with their recommended severity levels:
|
|
18
|
+
* - `no-message-interpolation`: error
|
|
19
|
+
* - `prefer-lazy-evaluation`: warn
|
|
20
|
+
* - `no-unawaited-log`: error
|
|
21
|
+
* - `require-meta-sink`: warn
|
|
22
|
+
*/
|
|
23
|
+
declare const recommended: Linter.Config;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { plugin as default, plugin, noMessageInterpolation, noUnawaitedLog, preferLazyEvaluation, recommended, requireMetaSink, rules };
|
|
26
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","names":[],"sources":["../../src/eslint/plugin.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;cA6Ca,OAAO,eAAe,IAAA,CAAK;cAS3B;;;;;;;;;;cAeA,aAAa,MAAA,CAAO"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { noMessageInterpolation } from "../rules/no-message-interpolation.js";
|
|
2
|
+
import { noUnawaitedLog } from "../rules/no-unawaited-log.js";
|
|
3
|
+
import { preferLazyEvaluation } from "../rules/prefer-lazy-evaluation.js";
|
|
4
|
+
import { requireMetaSink } from "../rules/require-meta-sink.js";
|
|
5
|
+
|
|
6
|
+
//#region src/eslint/plugin.ts
|
|
7
|
+
/**
|
|
8
|
+
* All LogTape lint rules.
|
|
9
|
+
*/
|
|
10
|
+
const rules = {
|
|
11
|
+
"no-message-interpolation": noMessageInterpolation,
|
|
12
|
+
"prefer-lazy-evaluation": preferLazyEvaluation,
|
|
13
|
+
"no-unawaited-log": noUnawaitedLog,
|
|
14
|
+
"require-meta-sink": requireMetaSink
|
|
15
|
+
};
|
|
16
|
+
const plugin = {
|
|
17
|
+
meta: { name: "@logtape/lint" },
|
|
18
|
+
rules,
|
|
19
|
+
configs: {}
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Recommended ESLint v9 flat config preset.
|
|
23
|
+
*
|
|
24
|
+
* Enables all LogTape rules with their recommended severity levels:
|
|
25
|
+
* - `no-message-interpolation`: error
|
|
26
|
+
* - `prefer-lazy-evaluation`: warn
|
|
27
|
+
* - `no-unawaited-log`: error
|
|
28
|
+
* - `require-meta-sink`: warn
|
|
29
|
+
*/
|
|
30
|
+
const recommended = {
|
|
31
|
+
plugins: { logtape: plugin },
|
|
32
|
+
rules: {
|
|
33
|
+
"logtape/no-message-interpolation": "error",
|
|
34
|
+
"logtape/prefer-lazy-evaluation": "warn",
|
|
35
|
+
"logtape/no-unawaited-log": "error",
|
|
36
|
+
"logtape/require-meta-sink": "warn"
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
plugin.configs.recommended = recommended;
|
|
40
|
+
var plugin_default = plugin;
|
|
41
|
+
|
|
42
|
+
//#endregion
|
|
43
|
+
export { plugin_default as default, noMessageInterpolation, noUnawaitedLog, plugin, preferLazyEvaluation, recommended, requireMetaSink, rules };
|
|
44
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","names":["rules: Record<string, Rule.RuleModule>","plugin: any","recommended: Linter.Config"],"sources":["../../src/eslint/plugin.ts"],"sourcesContent":["/**\n * ESLint + Oxlint plugin for LogTape lint rules.\n *\n * Works with ESLint v8 and v9+ flat config, as well as Oxlint which provides\n * an ESLint-compatible plugin API.\n *\n * **ESLint v9 flat config** (`eslint.config.js`):\n * ```js\n * import logtapePlugin from \"@logtape/lint/eslint\";\n * export default [\n * {\n * plugins: { logtape: logtapePlugin },\n * rules: {\n * \"logtape/no-message-interpolation\": \"error\",\n * \"logtape/prefer-lazy-evaluation\": \"warn\",\n * \"logtape/no-unawaited-log\": \"error\",\n * \"logtape/require-meta-sink\": \"warn\",\n * },\n * },\n * ];\n * ```\n *\n * **Using the recommended preset**:\n * ```js\n * import { recommended } from \"@logtape/lint/eslint\";\n * export default [recommended];\n * ```\n *\n * @module\n */\n\nimport type { Linter, Rule } from \"eslint\";\nimport { noMessageInterpolation } from \"../rules/no-message-interpolation.ts\";\nimport { noUnawaitedLog } from \"../rules/no-unawaited-log.ts\";\nimport { preferLazyEvaluation } from \"../rules/prefer-lazy-evaluation.ts\";\nimport { requireMetaSink } from \"../rules/require-meta-sink.ts\";\n\nexport { noMessageInterpolation } from \"../rules/no-message-interpolation.ts\";\nexport { noUnawaitedLog } from \"../rules/no-unawaited-log.ts\";\nexport { preferLazyEvaluation } from \"../rules/prefer-lazy-evaluation.ts\";\nexport { requireMetaSink } from \"../rules/require-meta-sink.ts\";\n\n/**\n * All LogTape lint rules.\n */\nexport const rules: Record<string, Rule.RuleModule> = {\n \"no-message-interpolation\": noMessageInterpolation,\n \"prefer-lazy-evaluation\": preferLazyEvaluation,\n \"no-unawaited-log\": noUnawaitedLog,\n \"require-meta-sink\": requireMetaSink,\n};\n\n// Declare plugin before recommended to allow the circular reference.\n// deno-lint-ignore no-explicit-any\nexport const plugin: any = {\n meta: { name: \"@logtape/lint\" },\n rules,\n configs: {},\n};\n\n/**\n * Recommended ESLint v9 flat config preset.\n *\n * Enables all LogTape rules with their recommended severity levels:\n * - `no-message-interpolation`: error\n * - `prefer-lazy-evaluation`: warn\n * - `no-unawaited-log`: error\n * - `require-meta-sink`: warn\n */\nexport const recommended: Linter.Config = {\n plugins: { logtape: plugin },\n rules: {\n \"logtape/no-message-interpolation\": \"error\",\n \"logtape/prefer-lazy-evaluation\": \"warn\",\n \"logtape/no-unawaited-log\": \"error\",\n \"logtape/require-meta-sink\": \"warn\",\n },\n};\n\nplugin.configs.recommended = recommended;\n\nexport default plugin;\n"],"mappings":";;;;;;;;;AA6CA,MAAaA,QAAyC;CACpD,4BAA4B;CAC5B,0BAA0B;CAC1B,oBAAoB;CACpB,qBAAqB;AACtB;AAID,MAAaC,SAAc;CACzB,MAAM,EAAE,MAAM,gBAAiB;CAC/B;CACA,SAAS,CAAE;AACZ;;;;;;;;;;AAWD,MAAaC,cAA6B;CACxC,SAAS,EAAE,SAAS,OAAQ;CAC5B,OAAO;EACL,oCAAoC;EACpC,kCAAkC;EAClC,4BAA4B;EAC5B,6BAA6B;CAC9B;AACF;AAED,OAAO,QAAQ,cAAc;AAE7B,qBAAe"}
|
package/dist/mod.cjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
|
+
const require_no_message_interpolation = require('./rules/no-message-interpolation.cjs');
|
|
3
|
+
const require_no_unawaited_log = require('./rules/no-unawaited-log.cjs');
|
|
4
|
+
const require_prefer_lazy_evaluation = require('./rules/prefer-lazy-evaluation.cjs');
|
|
5
|
+
const require_require_meta_sink = require('./rules/require-meta-sink.cjs');
|
|
6
|
+
const require_plugin = require('./eslint/plugin.cjs');
|
|
7
|
+
|
|
8
|
+
exports.default = require_plugin.plugin;
|
|
9
|
+
exports.noMessageInterpolation = require_no_message_interpolation.noMessageInterpolation;
|
|
10
|
+
exports.noUnawaitedLog = require_no_unawaited_log.noUnawaitedLog;
|
|
11
|
+
exports.plugin = require_plugin.plugin;
|
|
12
|
+
exports.preferLazyEvaluation = require_prefer_lazy_evaluation.preferLazyEvaluation;
|
|
13
|
+
exports.recommended = require_plugin.recommended;
|
|
14
|
+
exports.requireMetaSink = require_require_meta_sink.requireMetaSink;
|
|
15
|
+
exports.rules = require_plugin.rules;
|
package/dist/mod.d.cts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { noMessageInterpolation } from "./rules/no-message-interpolation.cjs";
|
|
2
|
+
import { noUnawaitedLog } from "./rules/no-unawaited-log.cjs";
|
|
3
|
+
import { preferLazyEvaluation } from "./rules/prefer-lazy-evaluation.cjs";
|
|
4
|
+
import { requireMetaSink } from "./rules/require-meta-sink.cjs";
|
|
5
|
+
import plugin, { recommended, rules } from "./eslint/plugin.cjs";
|
|
6
|
+
export { plugin as default, noMessageInterpolation, noUnawaitedLog, plugin, preferLazyEvaluation, recommended, requireMetaSink, rules };
|
package/dist/mod.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { noMessageInterpolation } from "./rules/no-message-interpolation.js";
|
|
2
|
+
import { noUnawaitedLog } from "./rules/no-unawaited-log.js";
|
|
3
|
+
import { preferLazyEvaluation } from "./rules/prefer-lazy-evaluation.js";
|
|
4
|
+
import { requireMetaSink } from "./rules/require-meta-sink.js";
|
|
5
|
+
import plugin, { recommended, rules } from "./eslint/plugin.js";
|
|
6
|
+
export { plugin as default, noMessageInterpolation, noUnawaitedLog, plugin, preferLazyEvaluation, recommended, requireMetaSink, rules };
|
package/dist/mod.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { noMessageInterpolation } from "./rules/no-message-interpolation.js";
|
|
2
|
+
import { noUnawaitedLog } from "./rules/no-unawaited-log.js";
|
|
3
|
+
import { preferLazyEvaluation } from "./rules/prefer-lazy-evaluation.js";
|
|
4
|
+
import { requireMetaSink } from "./rules/require-meta-sink.js";
|
|
5
|
+
import { plugin, recommended, rules } from "./eslint/plugin.js";
|
|
6
|
+
|
|
7
|
+
export { plugin as default, noMessageInterpolation, noUnawaitedLog, plugin, preferLazyEvaluation, recommended, requireMetaSink, rules };
|