@nodesecure/js-x-ray
Version:
JavaScript AST XRay analysis
221 lines • 8.94 kB
JavaScript
import { CALL_EXPRESSION_DATA } from "../contants.js";
import { generateWarning } from "../warnings.js";
import { toArrayLocation } from "../utils/toArrayLocation.js";
import { VariableTracer } from "../VariableTracer.js";
import { isIdentifier } from "../estree/index.js";
// CONSTANTS
const kLoggerTracedFunctions = Symbol("kRunLoggerTracedFunctions");
const kPinoLogMethods = ["info", "warn", "error", "fatal", "debug", "trace"];
const kWinstonLogMethods = ["info", "warn", "error", "http", "debug", "verbose", "silly", "log"];
const kThirdPartyLoggers = [{
moduleName: "winston",
identifierOrMemberExpr: "winston.createLogger"
},
{
moduleName: "winston",
identifierOrMemberExpr: "winston"
},
{
moduleName: "pino",
identifierOrMemberExpr: "pino"
}
];
function validateNode(_node, ctx) {
const identifierOrMemberExpr = ctx.context?.[CALL_EXPRESSION_DATA]?.identifierOrMemberExpr;
return [
ctx.context[kLoggerTracedFunctions].has(identifierOrMemberExpr),
identifierOrMemberExpr
];
}
function initialize(ctx) {
const { sourceFile } = ctx;
const logUsages = new Set(["console.log", "console.info", "console.warn", "console.error", "console.debug"]);
for (const logUsageMethod of logUsages) {
sourceFile.tracer.trace(logUsageMethod, {
followConsecutiveAssignment: true
});
}
for (const { moduleName, identifierOrMemberExpr } of kThirdPartyLoggers) {
sourceFile.tracer.trace(identifierOrMemberExpr, {
followReturnValueAssignement: true,
followConsecutiveAssignment: true,
moduleName
});
}
createWinstonTracerListener(sourceFile.tracer, logUsages);
createWinstonCreateLoggerTracerListener(sourceFile.tracer, logUsages);
createPinoTracerListener(sourceFile.tracer, logUsages);
ctx.context[kLoggerTracedFunctions] = logUsages;
}
function createWinstonTracerListener(tracer, logUsages) {
tracer.trace("winston.child", {
followReturnValueAssignement: true,
moduleName: "winston"
});
for (const method of kWinstonLogMethods) {
const loggerMethod = `winston.${method}`;
logUsages.add(loggerMethod);
tracer.trace(loggerMethod, {
followConsecutiveAssignment: true,
moduleName: "winston"
});
}
const winstonLoggerFactoryTracedFunctions = new Set(["winston.child"]);
tracer.on(VariableTracer.ReturnValueEvent, (payload) => {
if (!winstonLoggerFactoryTracedFunctions.has(payload.name)) {
return;
}
for (const method of kWinstonLogMethods) {
const infoTracedFunction = `${payload.id}.${method}`;
logUsages.add(infoTracedFunction);
tracer.trace(infoTracedFunction, {
followConsecutiveAssignment: true,
moduleName: "winston"
});
}
const childLogger = `${payload.id}.child`;
tracer.trace(childLogger, {
followReturnValueAssignement: true,
moduleName: "winston"
});
winstonLoggerFactoryTracedFunctions.add(childLogger);
});
}
function createWinstonCreateLoggerTracerListener(tracer, logUsages) {
const winstonCreateLoggerFactoryTracedFunctions = new Set(["winston.createLogger"]);
const winstonCreateLoggerChildLoggerFunctions = new Map();
tracer.on(VariableTracer.ReturnValueEvent, (payload) => {
if (!winstonCreateLoggerFactoryTracedFunctions.has(payload.name)) {
return;
}
let winstonLoggerMethods = winstonCreateLoggerChildLoggerFunctions.get(payload.name) ?? [...kWinstonLogMethods];
winston: if (payload.name === "winston.createLogger") {
const winstonContext = payload.arguments[0];
if (!winstonContext || winstonContext.type !== "ObjectExpression") {
break winston;
}
const levels = winstonContext.properties
.find((objEl) => objEl.type === "Property" && isIdentifier(objEl.key) && objEl.key.name === "levels");
if (!levels) {
break winston;
}
winstonLoggerMethods = [];
addLogMethods(levels, winstonLoggerMethods);
}
for (const method of winstonLoggerMethods) {
const infoTracedFunction = `${payload.id}.${method}`;
logUsages.add(infoTracedFunction);
tracer.trace(infoTracedFunction, {
followConsecutiveAssignment: true,
moduleName: "winston"
});
}
const childLogger = `${payload.id}.child`;
tracer.trace(childLogger, {
followReturnValueAssignement: true,
moduleName: "winston"
});
winstonCreateLoggerChildLoggerFunctions.set(childLogger, winstonLoggerMethods);
winstonCreateLoggerFactoryTracedFunctions.add(childLogger);
});
}
function createPinoTracerListener(tracer, logUsages) {
const pinoLoggerFactoryTracedFunctions = new Set(["pino"]);
const pinoLoggerChildLoggerFunctions = new Map();
tracer.on(VariableTracer.ReturnValueEvent, (payload) => {
if (!pinoLoggerFactoryTracedFunctions.has(payload.name)) {
return;
}
let pinoLoggerMethods = pinoLoggerChildLoggerFunctions.get(payload.name) ?? [...kPinoLogMethods];
pino: if (payload.name === "pino") {
const pinoContext = payload.arguments[0];
if (!pinoContext || pinoContext.type !== "ObjectExpression") {
break pino;
}
let customLevels;
let useOnlyCustomLevels;
for (const objectEl of pinoContext.properties) {
if (customLevels && useOnlyCustomLevels) {
break;
}
if (objectEl.type !== "Property"
|| objectEl.key.type !== "Identifier") {
continue;
}
if (objectEl.key.name === "customLevels") {
customLevels = objectEl;
}
if (objectEl.key.name === "useOnlyCustomLevels") {
useOnlyCustomLevels = objectEl;
}
}
let useOnlyCustomLevelsRaw;
if (useOnlyCustomLevels?.type === "Property" && useOnlyCustomLevels.value.type === "Literal") {
useOnlyCustomLevelsRaw = useOnlyCustomLevels.value.raw;
}
if (useOnlyCustomLevels?.type === "Property" && isIdentifier(useOnlyCustomLevels.value)) {
const resolvedIdentifer = tracer.literalIdentifiers.get(useOnlyCustomLevels.value.name);
useOnlyCustomLevelsRaw = resolvedIdentifer?.value;
}
if (useOnlyCustomLevelsRaw === "true") {
pinoLoggerMethods = [];
}
addLogMethods(customLevels, pinoLoggerMethods);
}
for (const method of pinoLoggerMethods) {
const infoTracedFunction = `${payload.id}.${method}`;
logUsages.add(infoTracedFunction);
tracer.trace(infoTracedFunction, {
followConsecutiveAssignment: true,
moduleName: "pino"
});
}
const childLogger = `${payload.id}.child`;
tracer.trace(childLogger, {
followReturnValueAssignement: true,
moduleName: "pino"
});
pinoLoggerChildLoggerFunctions.set(childLogger, pinoLoggerMethods);
pinoLoggerFactoryTracedFunctions.add(childLogger);
});
}
function addLogMethods(customLevels, loggerMethods) {
if (customLevels?.type !== "Property" || customLevels.value.type !== "ObjectExpression") {
return;
}
customLevels.value.properties.forEach((level) => {
if (level.type === "Property" && isIdentifier(level.key)) {
loggerMethods.push(level.key.name);
}
});
}
function main(node, ctx) {
const logIdentifer = ctx.data;
const arrayLocation = ctx.context?.[logIdentifer];
if (arrayLocation) {
arrayLocation.push(toArrayLocation(node.loc ?? undefined));
}
else {
ctx.context[logIdentifer] = [toArrayLocation(node.loc ?? undefined)];
}
}
function finalize(ctx) {
const { sourceFile, context } = ctx;
if (context && Object.keys(context).length > 0) {
const warning = generateWarning("log-usage", {
value: Object.keys(context).map((method) => method.replace(/__virtual_call_\d+__\./, "")).join(", ")
});
sourceFile.warnings.push({ ...warning, location: Object.values(context).flat() });
}
}
export default {
name: "log-usage",
nodeTypes: ["CallExpression"],
validateNode,
initialize,
main,
finalize,
breakOnMatch: false,
context: {}
};
//# sourceMappingURL=log-usage.js.map