console-sniper 1.0.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 +21 -0
- package/README.md +226 -0
- package/dist/cli/cli.js +493 -0
- package/dist/cli/cli.js.map +1 -0
- package/dist/index.cjs +250 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +93 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.js +204 -0
- package/dist/index.js.map +1 -0
- package/dist/types-J-TZoPXb.d.cts +133 -0
- package/dist/types-J-TZoPXb.d.ts +133 -0
- package/dist/vite/vitePlugin.cjs +289 -0
- package/dist/vite/vitePlugin.cjs.map +1 -0
- package/dist/vite/vitePlugin.d.cts +51 -0
- package/dist/vite/vitePlugin.d.ts +51 -0
- package/dist/vite/vitePlugin.js +252 -0
- package/dist/vite/vitePlugin.js.map +1 -0
- package/package.json +90 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/stripConsole.ts","../src/core/constants.ts","../src/core/utils.ts"],"sourcesContent":["/**\r\n * @file stripConsole.ts\r\n * @description The AST-based core engine that removes console statements.\r\n *\r\n * ─── Architecture Note ────────────────────────────────────────────────────────\r\n * This file is the heart of console-sniper. It has ZERO dependencies on Vite,\r\n * the CLI, or Node.js file system APIs. It works purely on source code strings.\r\n *\r\n * This isolation means the same engine can power:\r\n * • The Vite plugin (src/vite/vitePlugin.ts)\r\n * • The CLI (src/cli/cli.ts)\r\n * • Future: Rollup, Webpack, esbuild, Bun plugins\r\n * • Any programmatic usage\r\n * ─────────────────────────────────────────────────────────────────────────────\r\n *\r\n * How it works (at a high level):\r\n * 1. Parse the source code into an AST (Abstract Syntax Tree)\r\n * 2. Walk the AST looking for `ExpressionStatement` nodes where the\r\n * expression is a `CallExpression` like `console.log(...)`\r\n * 3. Remove those nodes from the AST\r\n * 4. Optionally scan and remove comments referencing \"console\"\r\n * 5. Re-generate source code from the modified AST\r\n */\r\n\r\nimport { parse, type ParserOptions } from \"@babel/parser\";\r\nimport _traverse from \"@babel/traverse\";\r\nimport _generate from \"@babel/generator\";\r\nimport * as t from \"@babel/types\";\r\n\r\nimport { CONSOLE_KEYWORD } from \"./constants.js\";\r\nimport { resolveOptionsSync } from \"./utils.js\";\r\nimport type { StripConsoleOptions, StripConsoleResult } from \"./types.js\";\r\n\r\n\r\n// CJS/ESM interop fix\r\n\r\n// @babel/traverse and @babel/generator are CommonJS packages. When this\r\n// module is bundled as ESM (or loaded via ts-node/vitest), the default\r\n// import may resolve to the module object rather than the callable function.\r\n// The actual function is always available on `.default` in that case.\r\n// We normalise once here so every call site just works.\r\nconst traverse = (\r\n typeof _traverse === \"function\" ? _traverse : (_traverse as any).default\r\n) as typeof _traverse;\r\n\r\nconst generate = (\r\n typeof _generate === \"function\" ? _generate : (_generate as any).default\r\n) as typeof _generate;\r\n\r\n\r\n// Public API\r\n\r\n/**\r\n * Strip `console.*` calls from a source code string using AST parsing.\r\n *\r\n * This is the primary public API of console-sniper. It's pure and stateless —\r\n * give it code, get transformed code back. Nothing is written to disk here.\r\n *\r\n * @param code - The source code to transform (JS, TS, JSX, TSX)\r\n * @param options - Optional configuration\r\n * @returns - The transformed code + metadata about what was removed\r\n *\r\n * @example\r\n * ```ts\r\n * const { code, removedCount, changed } = stripConsoleFromCode(`\r\n * console.log(\"hello\");\r\n * const x = 1 + 2;\r\n * console.warn(\"something\");\r\n * `);\r\n * // code → \"\\nconst x = 1 + 2;\\n\"\r\n * // removed → 2\r\n * // changed → true\r\n * ```\r\n */\r\nexport function stripConsoleFromCode(\r\n code: string,\r\n options?: StripConsoleOptions\r\n): StripConsoleResult {\r\n // 1. Resolve options (fills in defaults for anything not specified)\r\n const opts = resolveOptionsSync(options);\r\n const methodsToRemove = new Set(opts.methods);\r\n\r\n // Track what we remove for the metadata result\r\n const removedMethods: string[] = [];\r\n\r\n // ── Step 1: Parse ────────────────────────────────────────────\r\n // We need to tell Babel's parser about all the syntax we might encounter.\r\n // Using all these plugins means a single parser config handles every file\r\n // type (JS, TS, JSX, TSX) without needing per-file configuration.\r\n const parserOptions: ParserOptions = {\r\n sourceType: \"module\", // Support import/export statements\r\n strictMode: false, // Don't throw on sloppy-mode code\r\n allowImportExportEverywhere: true,\r\n allowReturnOutsideFunction: true,\r\n allowSuperOutsideMethod: true,\r\n plugins: [\r\n \"typescript\", // TypeScript syntax (types, generics, decorators)\r\n \"jsx\", // JSX/TSX syntax\r\n \"decorators\", // @decorator syntax\r\n \"classProperties\",\r\n \"classPrivateProperties\",\r\n \"classPrivateMethods\",\r\n \"classStaticBlock\",\r\n \"dynamicImport\", // import(...)\r\n \"exportDefaultFrom\",\r\n \"exportNamespaceFrom\",\r\n \"nullishCoalescingOperator\", // ??\r\n \"optionalChaining\", // ?.\r\n \"optionalCatchBinding\",\r\n \"logicalAssignment\", // &&=, ||=, ??=\r\n \"numericSeparator\", // 1_000_000\r\n \"bigInt\",\r\n \"importMeta\", // import.meta\r\n \"topLevelAwait\",\r\n ],\r\n };\r\n\r\n let ast: ReturnType<typeof parse>;\r\n\r\n try {\r\n ast = parse(code, parserOptions);\r\n } catch (parseError) {\r\n // If parsing fails (e.g. unsupported syntax), return the original code\r\n // unchanged rather than crashing. This is safer for production builds.\r\n if (!opts.silent) {\r\n console.warn(\r\n `[console-sniper] Failed to parse code, skipping. Error: ${\r\n parseError instanceof Error ? parseError.message : String(parseError)\r\n }`\r\n );\r\n }\r\n return {\r\n code,\r\n removedCount: 0,\r\n removedMethods: [],\r\n changed: false,\r\n };\r\n }\r\n\r\n // ── Step 2: Traverse & Remove Console Calls ─────────────────\r\n //\r\n // We look for nodes matching this AST pattern:\r\n //\r\n // ExpressionStatement\r\n // └── CallExpression\r\n // ├── callee: MemberExpression\r\n // │ ├── object: Identifier { name: \"console\" }\r\n // │ └── property: Identifier { name: \"log\" | \"warn\" | ... }\r\n // └── arguments: [...]\r\n //\r\n // Matching the full ExpressionStatement (the statement wrapper) rather than\r\n // just the CallExpression lets us safely remove the entire line — including\r\n // the trailing semicolon — without leaving orphan syntax behind.\r\n\r\n traverse(ast, {\r\n ExpressionStatement(nodePath) {\r\n const { expression } = nodePath.node;\r\n\r\n // Must be a function call\r\n if (!t.isCallExpression(expression)) return;\r\n\r\n const { callee } = expression;\r\n\r\n // The callee must be a member expression (object.method)\r\n if (!t.isMemberExpression(callee)) return;\r\n\r\n const { object, property } = callee;\r\n\r\n // The object must be `console` (an Identifier with name \"console\")\r\n if (!t.isIdentifier(object, { name: \"console\" })) return;\r\n\r\n // The property must be one of our configured methods\r\n // Property can be either an Identifier (console.log) or\r\n // a StringLiteral (console[\"log\"]) — we handle both.\r\n let methodName: string | undefined;\r\n\r\n if (t.isIdentifier(property)) {\r\n methodName = property.name;\r\n } else if (t.isStringLiteral(property)) {\r\n methodName = property.value;\r\n }\r\n\r\n if (!methodName || !methodsToRemove.has(methodName)) return;\r\n\r\n // ✅ This is a console call we should remove!\r\n removedMethods.push(methodName);\r\n\r\n // `nodePath.remove()` is the safe Babel way to delete a node.\r\n // It properly handles parent references, scope bindings, etc.\r\n nodePath.remove();\r\n },\r\n });\r\n\r\n // ── Step 3: Remove Console Comments ─────────────────────────\r\n //\r\n // Babel attaches comments to the nearest AST node as `leadingComments`\r\n // or `trailingComments`. We need to scan ALL nodes and filter out\r\n // any comments that mention \"console\".\r\n //\r\n // We do this AFTER the traverse so we don't interfere with node removal.\r\n\r\n if (opts.removeComments) {\r\n removeConsoleComments(ast);\r\n }\r\n\r\n // ── Step 4: Re-generate Source Code ─────────────────────────\r\n //\r\n // @babel/generator walks the (now modified) AST and prints it back to a\r\n // string. We enable `retainLines` to preserve line numbers as much as\r\n // possible — this keeps source maps accurate and diffs readable.\r\n\r\n const { code: transformedCode } = generate(\r\n ast,\r\n {\r\n retainLines: false, // Compact output; set true if you need line parity\r\n compact: false, // Keep human-readable whitespace\r\n concise: false,\r\n comments: true, // Include non-console comments\r\n jsescOption: {\r\n minimal: true, // Don't over-escape unicode\r\n },\r\n },\r\n code // Original source (used for sourcemap generation)\r\n );\r\n\r\n const removedCount = removedMethods.length;\r\n\r\n return {\r\n code: transformedCode,\r\n removedCount,\r\n removedMethods,\r\n changed: removedCount > 0,\r\n };\r\n}\r\n\r\n// Internal Helpers\r\n\r\n/**\r\n * Walk the AST and strip comments that reference \"console\".\r\n *\r\n * Babel stores comments as arrays on AST nodes:\r\n * node.leadingComments — comments before the node\r\n * node.innerComments — comments inside empty blocks\r\n * node.trailingComments — comments after the node\r\n *\r\n * We filter each array, removing comments whose `value` contains the\r\n * CONSOLE_KEYWORD (\"console\").\r\n *\r\n * @param ast - The parsed AST (mutated in place)\r\n */\r\nfunction removeConsoleComments(ast: t.File): void {\r\n /**\r\n * Filter a comment array, removing any that mention \"console\".\r\n */\r\n function filterComments(\r\n comments: t.Comment[] | null | undefined\r\n ): t.Comment[] | null {\r\n if (!comments || comments.length === 0) return comments ?? null;\r\n\r\n return comments.filter(\r\n (comment) => !comment.value.includes(CONSOLE_KEYWORD)\r\n );\r\n }\r\n\r\n // We need to visit every node in the AST.\r\n // `traverse` with no specific node type visits everything.\r\n traverse(ast, {\r\n enter(nodePath) {\r\n const { node } = nodePath;\r\n\r\n // Filter leading comments (e.g. `// console.log here`)\r\n if (node.leadingComments) {\r\n const filtered = filterComments(node.leadingComments);\r\n // Babel uses `null` when there are no comments\r\n (node as t.Node & { leadingComments: t.Comment[] | null }).leadingComments =\r\n filtered;\r\n }\r\n\r\n // Filter trailing comments (e.g. `} // end console block`)\r\n if (node.trailingComments) {\r\n const filtered = filterComments(node.trailingComments);\r\n (node as t.Node & { trailingComments: t.Comment[] | null }).trailingComments =\r\n filtered;\r\n }\r\n\r\n // Filter inner comments (e.g. comments inside empty blocks)\r\n if (node.innerComments) {\r\n const filtered = filterComments(node.innerComments);\r\n (node as t.Node & { innerComments: t.Comment[] | null }).innerComments =\r\n filtered;\r\n }\r\n },\r\n });\r\n}\r\n","/**\r\n * @file constants.ts\r\n * @description Global constants used across the project.\r\n *\r\n * Centralizing magic strings and default values here prevents typos\r\n * and makes it trivial to change defaults in one place.\r\n */\r\n\r\n\r\n// Package Identity\r\n\r\n/** The package name — used in logs, banners, and plugin names. */\r\nexport const PACKAGE_NAME = \"console-sniper\";\r\n\r\n/** Current version — ideally sync'd with package.json at build time. */\r\nexport const PACKAGE_VERSION = \"1.0.0\";\r\n\r\n\r\n// Console Stripping Defaults\r\n\r\n/**\r\n * Default console methods that are removed when `methods` is not specified.\r\n *\r\n * Extending this list is the primary way to support new console variants\r\n * like `console.table`, `console.time`, `console.group`, etc.\r\n */\r\nexport const DEFAULT_METHODS: readonly string[] = [\r\n \"log\",\r\n \"warn\",\r\n \"error\",\r\n \"info\",\r\n \"debug\",\r\n] as const;\r\n\r\n/**\r\n * Whether to remove comments containing \"console\" by default.\r\n */\r\nexport const DEFAULT_REMOVE_COMMENTS = true;\r\n\r\n\r\n// File Extension Filters\r\n\r\n/**\r\n * File extensions that the Babel parser can handle.\r\n * These are used by the CLI file scanner to filter which files to process.\r\n *\r\n * Note: The Babel parser handles TypeScript and JSX natively via plugins.\r\n */\r\nexport const SUPPORTED_EXTENSIONS: readonly string[] = [\r\n \".js\",\r\n \".mjs\",\r\n \".cjs\",\r\n \".jsx\",\r\n \".ts\",\r\n \".tsx\",\r\n \".mts\",\r\n \".cts\",\r\n] as const;\r\n\r\n/**\r\n * Default glob patterns for the CLI to EXCLUDE when scanning directories.\r\n */\r\nexport const DEFAULT_EXCLUDE_PATTERNS: readonly string[] = [\r\n \"**/node_modules/**\",\r\n \"**/dist/**\",\r\n \"**/build/**\",\r\n \"**/.git/**\",\r\n \"**/*.min.js\",\r\n \"**/*.d.ts\",\r\n] as const;\r\n\r\n// AST Parser Config\r\n\r\n/**\r\n * The word we look for when scanning comment text.\r\n * Keeping this as a constant prevents subtle bugs from typos.\r\n */\r\nexport const CONSOLE_KEYWORD = \"console\";\r\n","/**\r\n * @file utils.ts\r\n * @description Pure utility functions shared across the core engine.\r\n */\r\n\r\nimport path from \"node:path\";\r\nimport { SUPPORTED_EXTENSIONS, DEFAULT_METHODS, DEFAULT_REMOVE_COMMENTS } from \"./constants.js\";\r\nimport type { StripConsoleOptions } from \"./types.js\";\r\n\r\nexport function resolveOptionsSync(\r\n options?: StripConsoleOptions\r\n): StripConsoleOptions & {\r\n methods: string[];\r\n removeComments: boolean;\r\n silent: boolean;\r\n} {\r\n return {\r\n methods: options?.methods?.length ? options.methods : [...DEFAULT_METHODS],\r\n removeComments: options?.removeComments ?? DEFAULT_REMOVE_COMMENTS,\r\n include: options?.include ?? [],\r\n exclude: options?.exclude ?? [],\r\n silent: options?.silent ?? false,\r\n };\r\n}\r\n\r\nexport function isSupportedFile(filePath: string): boolean {\r\n const ext = path.extname(filePath).toLowerCase();\r\n return (SUPPORTED_EXTENSIONS as readonly string[]).includes(ext);\r\n}\r\n\r\nexport function shouldProcessFile(\r\n filePath: string,\r\n include: RegExp[] = [],\r\n exclude: RegExp[] = []\r\n): boolean {\r\n const normalized = filePath.replace(/\\\\/g, \"/\");\r\n if (include.length > 0 && !include.some((p) => p.test(normalized))) return false;\r\n if (exclude.length > 0 && exclude.some((p) => p.test(normalized))) return false;\r\n return true;\r\n}\r\n\r\nexport function parseMethodList(input: string): string[] {\r\n return input.split(\",\").map((m) => m.trim()).filter((m) => m.length > 0);\r\n}\r\n\r\nexport function formatCount(count: number, noun: string): string {\r\n return `${count} ${noun}${count !== 1 ? \"s\" : \"\"}`;\r\n}\r\n\r\nexport function isNode(): boolean {\r\n return typeof process !== \"undefined\" && process.versions?.node != null;\r\n}\r\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file types.ts
|
|
3
|
+
* @description All shared TypeScript types and interfaces for console-sniper.
|
|
4
|
+
*
|
|
5
|
+
* Keeping types in one place makes the project easier to navigate and ensures
|
|
6
|
+
* consistent interfaces between the core engine, CLI, and Vite plugin.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Options for the `stripConsoleFromCode` function.
|
|
10
|
+
*
|
|
11
|
+
* All fields are optional — sensible defaults are applied automatically.
|
|
12
|
+
*/
|
|
13
|
+
interface StripConsoleOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Which console methods to remove.
|
|
16
|
+
*
|
|
17
|
+
* @default ["log", "warn", "error", "info", "debug"]
|
|
18
|
+
* @example ["log", "warn"] — only strip console.log and console.warn
|
|
19
|
+
*/
|
|
20
|
+
methods?: string[];
|
|
21
|
+
/**
|
|
22
|
+
* Also remove single-line and multi-line comments that contain
|
|
23
|
+
* the word "console" or a console call like console.log(...).
|
|
24
|
+
*
|
|
25
|
+
* @default true
|
|
26
|
+
*/
|
|
27
|
+
removeComments?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* File path patterns to INCLUDE (used by CLI and Vite plugin for filtering).
|
|
30
|
+
* The core `stripConsoleFromCode` function ignores this — it's used upstream.
|
|
31
|
+
*
|
|
32
|
+
* @example [/\.tsx?$/, /\.jsx?$/]
|
|
33
|
+
*/
|
|
34
|
+
include?: RegExp[];
|
|
35
|
+
/**
|
|
36
|
+
* File path patterns to EXCLUDE (used by CLI and Vite plugin for filtering).
|
|
37
|
+
*
|
|
38
|
+
* @example [/node_modules/, /\.test\./]
|
|
39
|
+
*/
|
|
40
|
+
exclude?: RegExp[];
|
|
41
|
+
/**
|
|
42
|
+
* Suppress all log output (useful in programmatic use or tests).
|
|
43
|
+
*
|
|
44
|
+
* @default false
|
|
45
|
+
*/
|
|
46
|
+
silent?: boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Metadata returned by `stripConsoleFromCode` after processing a file.
|
|
50
|
+
*/
|
|
51
|
+
interface StripConsoleResult {
|
|
52
|
+
/**
|
|
53
|
+
* The transformed source code with console statements removed.
|
|
54
|
+
* If nothing was removed, this equals the original input.
|
|
55
|
+
*/
|
|
56
|
+
code: string;
|
|
57
|
+
/**
|
|
58
|
+
* Total number of console call expressions removed.
|
|
59
|
+
*/
|
|
60
|
+
removedCount: number;
|
|
61
|
+
/**
|
|
62
|
+
* The specific console methods that were removed (e.g. ["log", "warn"]).
|
|
63
|
+
* May contain duplicates if the same method appeared multiple times.
|
|
64
|
+
*/
|
|
65
|
+
removedMethods: string[];
|
|
66
|
+
/**
|
|
67
|
+
* `true` if the source code was modified, `false` if nothing changed.
|
|
68
|
+
* Useful for skipping file writes when nothing happened.
|
|
69
|
+
*/
|
|
70
|
+
changed: boolean;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Parsed CLI arguments after commander processes them.
|
|
74
|
+
*/
|
|
75
|
+
interface CliOptions {
|
|
76
|
+
/** Comma-separated list of methods to remove. */
|
|
77
|
+
methods: string;
|
|
78
|
+
/** Whether to remove comments referencing console. */
|
|
79
|
+
removeComments: boolean;
|
|
80
|
+
/** Glob patterns to exclude. */
|
|
81
|
+
exclude: string[];
|
|
82
|
+
/** Show a dry-run preview without writing files. */
|
|
83
|
+
dryRun: boolean;
|
|
84
|
+
/** Suppress output. */
|
|
85
|
+
silent: boolean;
|
|
86
|
+
/** Show verbose output (per-file stats). */
|
|
87
|
+
verbose: boolean;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Options accepted by the Vite plugin.
|
|
91
|
+
* Extends StripConsoleOptions with plugin-specific fields.
|
|
92
|
+
*/
|
|
93
|
+
interface VitePluginOptions extends StripConsoleOptions {
|
|
94
|
+
/**
|
|
95
|
+
* Only run in production builds.
|
|
96
|
+
* Set to `false` to also strip in dev mode (not recommended).
|
|
97
|
+
*
|
|
98
|
+
* @default true
|
|
99
|
+
*/
|
|
100
|
+
productionOnly?: boolean;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Result of processing a single file via CLI or batch mode.
|
|
104
|
+
*/
|
|
105
|
+
interface FileProcessResult {
|
|
106
|
+
/** Absolute path to the file. */
|
|
107
|
+
filePath: string;
|
|
108
|
+
/** Whether the file was modified. */
|
|
109
|
+
changed: boolean;
|
|
110
|
+
/** Number of console calls removed from this file. */
|
|
111
|
+
removedCount: number;
|
|
112
|
+
/** Methods that were removed. */
|
|
113
|
+
removedMethods: string[];
|
|
114
|
+
/** Whether this was a dry-run (file not actually written). */
|
|
115
|
+
dryRun: boolean;
|
|
116
|
+
/** Any error that occurred (null if successful). */
|
|
117
|
+
error: Error | null;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Summary stats after processing all files.
|
|
121
|
+
*/
|
|
122
|
+
interface BatchSummary {
|
|
123
|
+
/** Total files scanned. */
|
|
124
|
+
totalFiles: number;
|
|
125
|
+
/** Files where something was changed. */
|
|
126
|
+
modifiedFiles: number;
|
|
127
|
+
/** Files that had errors. */
|
|
128
|
+
errorFiles: number;
|
|
129
|
+
/** Total console calls removed across all files. */
|
|
130
|
+
totalRemoved: number;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export type { BatchSummary as B, CliOptions as C, FileProcessResult as F, StripConsoleOptions as S, VitePluginOptions as V, StripConsoleResult as a };
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file types.ts
|
|
3
|
+
* @description All shared TypeScript types and interfaces for console-sniper.
|
|
4
|
+
*
|
|
5
|
+
* Keeping types in one place makes the project easier to navigate and ensures
|
|
6
|
+
* consistent interfaces between the core engine, CLI, and Vite plugin.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Options for the `stripConsoleFromCode` function.
|
|
10
|
+
*
|
|
11
|
+
* All fields are optional — sensible defaults are applied automatically.
|
|
12
|
+
*/
|
|
13
|
+
interface StripConsoleOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Which console methods to remove.
|
|
16
|
+
*
|
|
17
|
+
* @default ["log", "warn", "error", "info", "debug"]
|
|
18
|
+
* @example ["log", "warn"] — only strip console.log and console.warn
|
|
19
|
+
*/
|
|
20
|
+
methods?: string[];
|
|
21
|
+
/**
|
|
22
|
+
* Also remove single-line and multi-line comments that contain
|
|
23
|
+
* the word "console" or a console call like console.log(...).
|
|
24
|
+
*
|
|
25
|
+
* @default true
|
|
26
|
+
*/
|
|
27
|
+
removeComments?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* File path patterns to INCLUDE (used by CLI and Vite plugin for filtering).
|
|
30
|
+
* The core `stripConsoleFromCode` function ignores this — it's used upstream.
|
|
31
|
+
*
|
|
32
|
+
* @example [/\.tsx?$/, /\.jsx?$/]
|
|
33
|
+
*/
|
|
34
|
+
include?: RegExp[];
|
|
35
|
+
/**
|
|
36
|
+
* File path patterns to EXCLUDE (used by CLI and Vite plugin for filtering).
|
|
37
|
+
*
|
|
38
|
+
* @example [/node_modules/, /\.test\./]
|
|
39
|
+
*/
|
|
40
|
+
exclude?: RegExp[];
|
|
41
|
+
/**
|
|
42
|
+
* Suppress all log output (useful in programmatic use or tests).
|
|
43
|
+
*
|
|
44
|
+
* @default false
|
|
45
|
+
*/
|
|
46
|
+
silent?: boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Metadata returned by `stripConsoleFromCode` after processing a file.
|
|
50
|
+
*/
|
|
51
|
+
interface StripConsoleResult {
|
|
52
|
+
/**
|
|
53
|
+
* The transformed source code with console statements removed.
|
|
54
|
+
* If nothing was removed, this equals the original input.
|
|
55
|
+
*/
|
|
56
|
+
code: string;
|
|
57
|
+
/**
|
|
58
|
+
* Total number of console call expressions removed.
|
|
59
|
+
*/
|
|
60
|
+
removedCount: number;
|
|
61
|
+
/**
|
|
62
|
+
* The specific console methods that were removed (e.g. ["log", "warn"]).
|
|
63
|
+
* May contain duplicates if the same method appeared multiple times.
|
|
64
|
+
*/
|
|
65
|
+
removedMethods: string[];
|
|
66
|
+
/**
|
|
67
|
+
* `true` if the source code was modified, `false` if nothing changed.
|
|
68
|
+
* Useful for skipping file writes when nothing happened.
|
|
69
|
+
*/
|
|
70
|
+
changed: boolean;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Parsed CLI arguments after commander processes them.
|
|
74
|
+
*/
|
|
75
|
+
interface CliOptions {
|
|
76
|
+
/** Comma-separated list of methods to remove. */
|
|
77
|
+
methods: string;
|
|
78
|
+
/** Whether to remove comments referencing console. */
|
|
79
|
+
removeComments: boolean;
|
|
80
|
+
/** Glob patterns to exclude. */
|
|
81
|
+
exclude: string[];
|
|
82
|
+
/** Show a dry-run preview without writing files. */
|
|
83
|
+
dryRun: boolean;
|
|
84
|
+
/** Suppress output. */
|
|
85
|
+
silent: boolean;
|
|
86
|
+
/** Show verbose output (per-file stats). */
|
|
87
|
+
verbose: boolean;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Options accepted by the Vite plugin.
|
|
91
|
+
* Extends StripConsoleOptions with plugin-specific fields.
|
|
92
|
+
*/
|
|
93
|
+
interface VitePluginOptions extends StripConsoleOptions {
|
|
94
|
+
/**
|
|
95
|
+
* Only run in production builds.
|
|
96
|
+
* Set to `false` to also strip in dev mode (not recommended).
|
|
97
|
+
*
|
|
98
|
+
* @default true
|
|
99
|
+
*/
|
|
100
|
+
productionOnly?: boolean;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Result of processing a single file via CLI or batch mode.
|
|
104
|
+
*/
|
|
105
|
+
interface FileProcessResult {
|
|
106
|
+
/** Absolute path to the file. */
|
|
107
|
+
filePath: string;
|
|
108
|
+
/** Whether the file was modified. */
|
|
109
|
+
changed: boolean;
|
|
110
|
+
/** Number of console calls removed from this file. */
|
|
111
|
+
removedCount: number;
|
|
112
|
+
/** Methods that were removed. */
|
|
113
|
+
removedMethods: string[];
|
|
114
|
+
/** Whether this was a dry-run (file not actually written). */
|
|
115
|
+
dryRun: boolean;
|
|
116
|
+
/** Any error that occurred (null if successful). */
|
|
117
|
+
error: Error | null;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Summary stats after processing all files.
|
|
121
|
+
*/
|
|
122
|
+
interface BatchSummary {
|
|
123
|
+
/** Total files scanned. */
|
|
124
|
+
totalFiles: number;
|
|
125
|
+
/** Files where something was changed. */
|
|
126
|
+
modifiedFiles: number;
|
|
127
|
+
/** Files that had errors. */
|
|
128
|
+
errorFiles: number;
|
|
129
|
+
/** Total console calls removed across all files. */
|
|
130
|
+
totalRemoved: number;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export type { BatchSummary as B, CliOptions as C, FileProcessResult as F, StripConsoleOptions as S, VitePluginOptions as V, StripConsoleResult as a };
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/vite/vitePlugin.ts
|
|
31
|
+
var vitePlugin_exports = {};
|
|
32
|
+
__export(vitePlugin_exports, {
|
|
33
|
+
consoleSniper: () => consoleSniper,
|
|
34
|
+
default: () => vitePlugin_default
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(vitePlugin_exports);
|
|
37
|
+
|
|
38
|
+
// src/core/stripConsole.ts
|
|
39
|
+
var import_parser = require("@babel/parser");
|
|
40
|
+
var import_traverse = __toESM(require("@babel/traverse"), 1);
|
|
41
|
+
var import_generator = __toESM(require("@babel/generator"), 1);
|
|
42
|
+
var t = __toESM(require("@babel/types"), 1);
|
|
43
|
+
|
|
44
|
+
// src/core/constants.ts
|
|
45
|
+
var PACKAGE_NAME = "console-sniper";
|
|
46
|
+
var DEFAULT_METHODS = [
|
|
47
|
+
"log",
|
|
48
|
+
"warn",
|
|
49
|
+
"error",
|
|
50
|
+
"info",
|
|
51
|
+
"debug"
|
|
52
|
+
];
|
|
53
|
+
var DEFAULT_REMOVE_COMMENTS = true;
|
|
54
|
+
var SUPPORTED_EXTENSIONS = [
|
|
55
|
+
".js",
|
|
56
|
+
".mjs",
|
|
57
|
+
".cjs",
|
|
58
|
+
".jsx",
|
|
59
|
+
".ts",
|
|
60
|
+
".tsx",
|
|
61
|
+
".mts",
|
|
62
|
+
".cts"
|
|
63
|
+
];
|
|
64
|
+
var CONSOLE_KEYWORD = "console";
|
|
65
|
+
|
|
66
|
+
// src/core/utils.ts
|
|
67
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
68
|
+
function resolveOptionsSync(options) {
|
|
69
|
+
return {
|
|
70
|
+
methods: options?.methods?.length ? options.methods : [...DEFAULT_METHODS],
|
|
71
|
+
removeComments: options?.removeComments ?? DEFAULT_REMOVE_COMMENTS,
|
|
72
|
+
include: options?.include ?? [],
|
|
73
|
+
exclude: options?.exclude ?? [],
|
|
74
|
+
silent: options?.silent ?? false
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function isSupportedFile(filePath) {
|
|
78
|
+
const ext = import_node_path.default.extname(filePath).toLowerCase();
|
|
79
|
+
return SUPPORTED_EXTENSIONS.includes(ext);
|
|
80
|
+
}
|
|
81
|
+
function shouldProcessFile(filePath, include = [], exclude = []) {
|
|
82
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
83
|
+
if (include.length > 0 && !include.some((p) => p.test(normalized))) return false;
|
|
84
|
+
if (exclude.length > 0 && exclude.some((p) => p.test(normalized))) return false;
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/core/stripConsole.ts
|
|
89
|
+
var traverse = typeof import_traverse.default === "function" ? import_traverse.default : import_traverse.default.default;
|
|
90
|
+
var generate = typeof import_generator.default === "function" ? import_generator.default : import_generator.default.default;
|
|
91
|
+
function stripConsoleFromCode(code, options) {
|
|
92
|
+
const opts = resolveOptionsSync(options);
|
|
93
|
+
const methodsToRemove = new Set(opts.methods);
|
|
94
|
+
const removedMethods = [];
|
|
95
|
+
const parserOptions = {
|
|
96
|
+
sourceType: "module",
|
|
97
|
+
// Support import/export statements
|
|
98
|
+
strictMode: false,
|
|
99
|
+
// Don't throw on sloppy-mode code
|
|
100
|
+
allowImportExportEverywhere: true,
|
|
101
|
+
allowReturnOutsideFunction: true,
|
|
102
|
+
allowSuperOutsideMethod: true,
|
|
103
|
+
plugins: [
|
|
104
|
+
"typescript",
|
|
105
|
+
// TypeScript syntax (types, generics, decorators)
|
|
106
|
+
"jsx",
|
|
107
|
+
// JSX/TSX syntax
|
|
108
|
+
"decorators",
|
|
109
|
+
// @decorator syntax
|
|
110
|
+
"classProperties",
|
|
111
|
+
"classPrivateProperties",
|
|
112
|
+
"classPrivateMethods",
|
|
113
|
+
"classStaticBlock",
|
|
114
|
+
"dynamicImport",
|
|
115
|
+
// import(...)
|
|
116
|
+
"exportDefaultFrom",
|
|
117
|
+
"exportNamespaceFrom",
|
|
118
|
+
"nullishCoalescingOperator",
|
|
119
|
+
// ??
|
|
120
|
+
"optionalChaining",
|
|
121
|
+
// ?.
|
|
122
|
+
"optionalCatchBinding",
|
|
123
|
+
"logicalAssignment",
|
|
124
|
+
// &&=, ||=, ??=
|
|
125
|
+
"numericSeparator",
|
|
126
|
+
// 1_000_000
|
|
127
|
+
"bigInt",
|
|
128
|
+
"importMeta",
|
|
129
|
+
// import.meta
|
|
130
|
+
"topLevelAwait"
|
|
131
|
+
]
|
|
132
|
+
};
|
|
133
|
+
let ast;
|
|
134
|
+
try {
|
|
135
|
+
ast = (0, import_parser.parse)(code, parserOptions);
|
|
136
|
+
} catch (parseError) {
|
|
137
|
+
if (!opts.silent) {
|
|
138
|
+
console.warn(
|
|
139
|
+
`[console-sniper] Failed to parse code, skipping. Error: ${parseError instanceof Error ? parseError.message : String(parseError)}`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
code,
|
|
144
|
+
removedCount: 0,
|
|
145
|
+
removedMethods: [],
|
|
146
|
+
changed: false
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
traverse(ast, {
|
|
150
|
+
ExpressionStatement(nodePath) {
|
|
151
|
+
const { expression } = nodePath.node;
|
|
152
|
+
if (!t.isCallExpression(expression)) return;
|
|
153
|
+
const { callee } = expression;
|
|
154
|
+
if (!t.isMemberExpression(callee)) return;
|
|
155
|
+
const { object, property } = callee;
|
|
156
|
+
if (!t.isIdentifier(object, { name: "console" })) return;
|
|
157
|
+
let methodName;
|
|
158
|
+
if (t.isIdentifier(property)) {
|
|
159
|
+
methodName = property.name;
|
|
160
|
+
} else if (t.isStringLiteral(property)) {
|
|
161
|
+
methodName = property.value;
|
|
162
|
+
}
|
|
163
|
+
if (!methodName || !methodsToRemove.has(methodName)) return;
|
|
164
|
+
removedMethods.push(methodName);
|
|
165
|
+
nodePath.remove();
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
if (opts.removeComments) {
|
|
169
|
+
removeConsoleComments(ast);
|
|
170
|
+
}
|
|
171
|
+
const { code: transformedCode } = generate(
|
|
172
|
+
ast,
|
|
173
|
+
{
|
|
174
|
+
retainLines: false,
|
|
175
|
+
// Compact output; set true if you need line parity
|
|
176
|
+
compact: false,
|
|
177
|
+
// Keep human-readable whitespace
|
|
178
|
+
concise: false,
|
|
179
|
+
comments: true,
|
|
180
|
+
// Include non-console comments
|
|
181
|
+
jsescOption: {
|
|
182
|
+
minimal: true
|
|
183
|
+
// Don't over-escape unicode
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
code
|
|
187
|
+
// Original source (used for sourcemap generation)
|
|
188
|
+
);
|
|
189
|
+
const removedCount = removedMethods.length;
|
|
190
|
+
return {
|
|
191
|
+
code: transformedCode,
|
|
192
|
+
removedCount,
|
|
193
|
+
removedMethods,
|
|
194
|
+
changed: removedCount > 0
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function removeConsoleComments(ast) {
|
|
198
|
+
function filterComments(comments) {
|
|
199
|
+
if (!comments || comments.length === 0) return comments ?? null;
|
|
200
|
+
return comments.filter(
|
|
201
|
+
(comment) => !comment.value.includes(CONSOLE_KEYWORD)
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
traverse(ast, {
|
|
205
|
+
enter(nodePath) {
|
|
206
|
+
const { node } = nodePath;
|
|
207
|
+
if (node.leadingComments) {
|
|
208
|
+
const filtered = filterComments(node.leadingComments);
|
|
209
|
+
node.leadingComments = filtered;
|
|
210
|
+
}
|
|
211
|
+
if (node.trailingComments) {
|
|
212
|
+
const filtered = filterComments(node.trailingComments);
|
|
213
|
+
node.trailingComments = filtered;
|
|
214
|
+
}
|
|
215
|
+
if (node.innerComments) {
|
|
216
|
+
const filtered = filterComments(node.innerComments);
|
|
217
|
+
node.innerComments = filtered;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/vite/vitePlugin.ts
|
|
224
|
+
function consoleSniper(options = {}) {
|
|
225
|
+
const {
|
|
226
|
+
productionOnly = true,
|
|
227
|
+
// Only strip in production by default
|
|
228
|
+
methods,
|
|
229
|
+
removeComments,
|
|
230
|
+
include = [],
|
|
231
|
+
exclude = [],
|
|
232
|
+
silent = false
|
|
233
|
+
} = options;
|
|
234
|
+
let isProduction = false;
|
|
235
|
+
return {
|
|
236
|
+
// ── Plugin metadata ─────────────────────────────────────
|
|
237
|
+
name: PACKAGE_NAME,
|
|
238
|
+
// `enforce: "pre"` makes our transform run before other plugins.
|
|
239
|
+
// This is important because we want to strip consoles from the
|
|
240
|
+
// original source, not from already-transformed code.
|
|
241
|
+
enforce: "pre",
|
|
242
|
+
// ── configResolved hook ─────────────────────────────────
|
|
243
|
+
// Called once after Vite has merged all config. This is where we
|
|
244
|
+
// learn whether we're in production or development mode.
|
|
245
|
+
configResolved(config) {
|
|
246
|
+
isProduction = config.command === "build" && config.mode === "production";
|
|
247
|
+
if (config.command === "build" && !config.mode) {
|
|
248
|
+
isProduction = true;
|
|
249
|
+
}
|
|
250
|
+
if (!silent && !productionOnly) {
|
|
251
|
+
console.warn(
|
|
252
|
+
`[${PACKAGE_NAME}] productionOnly is false \u2014 console statements will be removed in development mode too.`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
// ── transform hook ─────────────────────────────────────
|
|
257
|
+
// Called for every module that Vite processes.
|
|
258
|
+
// Return `null` to pass the file through unchanged.
|
|
259
|
+
// Return `{ code, map }` to provide a transformation.
|
|
260
|
+
transform(code, id) {
|
|
261
|
+
if (productionOnly && !isProduction) return null;
|
|
262
|
+
if (!isSupportedFile(id)) return null;
|
|
263
|
+
if (id.startsWith("\0")) return null;
|
|
264
|
+
if (!shouldProcessFile(id, include, exclude)) return null;
|
|
265
|
+
const stripOptions = { silent: true };
|
|
266
|
+
if (methods) stripOptions.methods = methods;
|
|
267
|
+
if (removeComments !== void 0) stripOptions.removeComments = removeComments;
|
|
268
|
+
const result = stripConsoleFromCode(code, stripOptions);
|
|
269
|
+
if (!result.changed) return null;
|
|
270
|
+
if (!silent && result.removedCount > 0) {
|
|
271
|
+
const shortId = id.split("/").slice(-2).join("/");
|
|
272
|
+
console.log(
|
|
273
|
+
`[${PACKAGE_NAME}] Removed ${result.removedCount} console call(s) from ${shortId}`
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
code: result.code,
|
|
278
|
+
// map: null tells Vite to skip source map merging for this transform
|
|
279
|
+
map: null
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
var vitePlugin_default = consoleSniper;
|
|
285
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
286
|
+
0 && (module.exports = {
|
|
287
|
+
consoleSniper
|
|
288
|
+
});
|
|
289
|
+
//# sourceMappingURL=vitePlugin.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/vite/vitePlugin.ts","../../src/core/stripConsole.ts","../../src/core/constants.ts","../../src/core/utils.ts"],"sourcesContent":["/**\r\n * @file vitePlugin.ts\r\n * @description Vite plugin that strips console.* statements during builds.\r\n *\r\n * ─── How Vite plugins work ────────────────────────────────────────────────────\r\n * Vite's plugin system is based on Rollup's. Each plugin can hook into\r\n * different stages of the build lifecycle. We use the `transform` hook,\r\n * which is called once per module with its source code and file ID.\r\n *\r\n * Our plugin:\r\n * 1. Checks if the file should be processed (by extension + include/exclude)\r\n * 2. Calls the core `stripConsoleFromCode` engine\r\n * 3. Returns the transformed code (or null to skip the file)\r\n *\r\n * ─── Why Vite is in a separate file ─────────────────────────────────────────\r\n * The core engine has ZERO Vite dependency. This means:\r\n * - Users who only use the CLI don't pull in Vite types\r\n * - The same engine can be used in Rollup, Webpack, or esbuild plugins\r\n * - Unit tests can test the core without mocking Vite\r\n * ─────────────────────────────────────────────────────────────────────────────\r\n */\r\n\r\nimport type { Plugin } from \"vite\";\r\nimport { stripConsoleFromCode } from \"../core/stripConsole.js\";\r\nimport { shouldProcessFile, isSupportedFile } from \"../core/utils.js\";\r\nimport { PACKAGE_NAME } from \"../core/constants.js\";\r\nimport type { VitePluginOptions } from \"../core/types.js\";\r\nimport type { StripConsoleOptions } from \"../core/types.js\";\r\n\r\n\r\n// Plugin Factory\r\n\r\n\r\n/**\r\n * A Vite plugin that removes `console.*` statements from source files\r\n * during the build process using AST-based transformation.\r\n *\r\n * @param options - Plugin configuration options\r\n * @returns - A Vite plugin object\r\n *\r\n * @example\r\n * ```ts\r\n * // vite.config.ts\r\n * import { defineConfig } from \"vite\";\r\n * import consoleSniper from \"console-sniper/vite\";\r\n *\r\n * export default defineConfig({\r\n * plugins: [\r\n * consoleSniper({\r\n * methods: [\"log\", \"warn\"],\r\n * productionOnly: true,\r\n * }),\r\n * ],\r\n * });\r\n * ```\r\n */\r\nexport function consoleSniper(options: VitePluginOptions = {}): Plugin {\r\n const {\r\n productionOnly = true, // Only strip in production by default\r\n methods,\r\n removeComments,\r\n include = [],\r\n exclude = [],\r\n silent = false,\r\n } = options;\r\n\r\n // This flag is set when the plugin initializes — we need the Vite mode\r\n // to decide whether to strip console calls.\r\n let isProduction = false;\r\n\r\n return {\r\n // ── Plugin metadata ─────────────────────────────────────\r\n name: PACKAGE_NAME,\r\n\r\n // `enforce: \"pre\"` makes our transform run before other plugins.\r\n // This is important because we want to strip consoles from the\r\n // original source, not from already-transformed code.\r\n enforce: \"pre\",\r\n\r\n // ── configResolved hook ─────────────────────────────────\r\n // Called once after Vite has merged all config. This is where we\r\n // learn whether we're in production or development mode.\r\n configResolved(config) {\r\n isProduction = config.command === \"build\" && config.mode === \"production\";\r\n\r\n // Also treat \"build\" with mode not set as production\r\n if (config.command === \"build\" && !config.mode) {\r\n isProduction = true;\r\n }\r\n\r\n if (!silent && !productionOnly) {\r\n // Warn the user if they're stripping in dev mode\r\n console.warn(\r\n `[${PACKAGE_NAME}] productionOnly is false — console statements will be removed in development mode too.`\r\n );\r\n }\r\n },\r\n\r\n // ── transform hook ─────────────────────────────────────\r\n // Called for every module that Vite processes.\r\n // Return `null` to pass the file through unchanged.\r\n // Return `{ code, map }` to provide a transformation.\r\n transform(code, id) {\r\n // Skip if productionOnly and we're not in production\r\n if (productionOnly && !isProduction) return null;\r\n\r\n // Skip files with unsupported extensions\r\n if (!isSupportedFile(id)) return null;\r\n\r\n // Skip virtual modules (Vite internal modules start with \\0)\r\n if (id.startsWith(\"\\0\")) return null;\r\n\r\n // Apply include/exclude patterns\r\n if (!shouldProcessFile(id, include, exclude)) return null;\r\n\r\n // ── Run the core AST engine ─────────────────────────\r\n const stripOptions: StripConsoleOptions = { silent: true };\r\n if (methods) stripOptions.methods = methods;\r\n if (removeComments !== undefined) stripOptions.removeComments = removeComments;\r\n const result = stripConsoleFromCode(code, stripOptions);\r\n\r\n // If nothing changed, return null (Vite will use the original)\r\n if (!result.changed) return null;\r\n\r\n // Log what we removed (only if not silent)\r\n if (!silent && result.removedCount > 0) {\r\n const shortId = id.split(\"/\").slice(-2).join(\"/\");\r\n console.log(\r\n `[${PACKAGE_NAME}] Removed ${result.removedCount} console call(s) from ${shortId}`\r\n );\r\n }\r\n\r\n // Return transformed code.\r\n // Note: We don't return a source map here because @babel/generator\r\n // can generate one — this is left as a future enhancement.\r\n return {\r\n code: result.code,\r\n // map: null tells Vite to skip source map merging for this transform\r\n map: null,\r\n };\r\n },\r\n };\r\n}\r\n\r\n\r\n// Default Export\r\n\r\n\r\n// Allow both named and default imports:\r\n// import consoleSniper from \"console-sniper/vite\"\r\n// import { consoleSniper } from \"console-sniper/vite\"\r\nexport default consoleSniper;\r\n","/**\r\n * @file stripConsole.ts\r\n * @description The AST-based core engine that removes console statements.\r\n *\r\n * ─── Architecture Note ────────────────────────────────────────────────────────\r\n * This file is the heart of console-sniper. It has ZERO dependencies on Vite,\r\n * the CLI, or Node.js file system APIs. It works purely on source code strings.\r\n *\r\n * This isolation means the same engine can power:\r\n * • The Vite plugin (src/vite/vitePlugin.ts)\r\n * • The CLI (src/cli/cli.ts)\r\n * • Future: Rollup, Webpack, esbuild, Bun plugins\r\n * • Any programmatic usage\r\n * ─────────────────────────────────────────────────────────────────────────────\r\n *\r\n * How it works (at a high level):\r\n * 1. Parse the source code into an AST (Abstract Syntax Tree)\r\n * 2. Walk the AST looking for `ExpressionStatement` nodes where the\r\n * expression is a `CallExpression` like `console.log(...)`\r\n * 3. Remove those nodes from the AST\r\n * 4. Optionally scan and remove comments referencing \"console\"\r\n * 5. Re-generate source code from the modified AST\r\n */\r\n\r\nimport { parse, type ParserOptions } from \"@babel/parser\";\r\nimport _traverse from \"@babel/traverse\";\r\nimport _generate from \"@babel/generator\";\r\nimport * as t from \"@babel/types\";\r\n\r\nimport { CONSOLE_KEYWORD } from \"./constants.js\";\r\nimport { resolveOptionsSync } from \"./utils.js\";\r\nimport type { StripConsoleOptions, StripConsoleResult } from \"./types.js\";\r\n\r\n\r\n// CJS/ESM interop fix\r\n\r\n// @babel/traverse and @babel/generator are CommonJS packages. When this\r\n// module is bundled as ESM (or loaded via ts-node/vitest), the default\r\n// import may resolve to the module object rather than the callable function.\r\n// The actual function is always available on `.default` in that case.\r\n// We normalise once here so every call site just works.\r\nconst traverse = (\r\n typeof _traverse === \"function\" ? _traverse : (_traverse as any).default\r\n) as typeof _traverse;\r\n\r\nconst generate = (\r\n typeof _generate === \"function\" ? _generate : (_generate as any).default\r\n) as typeof _generate;\r\n\r\n\r\n// Public API\r\n\r\n/**\r\n * Strip `console.*` calls from a source code string using AST parsing.\r\n *\r\n * This is the primary public API of console-sniper. It's pure and stateless —\r\n * give it code, get transformed code back. Nothing is written to disk here.\r\n *\r\n * @param code - The source code to transform (JS, TS, JSX, TSX)\r\n * @param options - Optional configuration\r\n * @returns - The transformed code + metadata about what was removed\r\n *\r\n * @example\r\n * ```ts\r\n * const { code, removedCount, changed } = stripConsoleFromCode(`\r\n * console.log(\"hello\");\r\n * const x = 1 + 2;\r\n * console.warn(\"something\");\r\n * `);\r\n * // code → \"\\nconst x = 1 + 2;\\n\"\r\n * // removed → 2\r\n * // changed → true\r\n * ```\r\n */\r\nexport function stripConsoleFromCode(\r\n code: string,\r\n options?: StripConsoleOptions\r\n): StripConsoleResult {\r\n // 1. Resolve options (fills in defaults for anything not specified)\r\n const opts = resolveOptionsSync(options);\r\n const methodsToRemove = new Set(opts.methods);\r\n\r\n // Track what we remove for the metadata result\r\n const removedMethods: string[] = [];\r\n\r\n // ── Step 1: Parse ────────────────────────────────────────────\r\n // We need to tell Babel's parser about all the syntax we might encounter.\r\n // Using all these plugins means a single parser config handles every file\r\n // type (JS, TS, JSX, TSX) without needing per-file configuration.\r\n const parserOptions: ParserOptions = {\r\n sourceType: \"module\", // Support import/export statements\r\n strictMode: false, // Don't throw on sloppy-mode code\r\n allowImportExportEverywhere: true,\r\n allowReturnOutsideFunction: true,\r\n allowSuperOutsideMethod: true,\r\n plugins: [\r\n \"typescript\", // TypeScript syntax (types, generics, decorators)\r\n \"jsx\", // JSX/TSX syntax\r\n \"decorators\", // @decorator syntax\r\n \"classProperties\",\r\n \"classPrivateProperties\",\r\n \"classPrivateMethods\",\r\n \"classStaticBlock\",\r\n \"dynamicImport\", // import(...)\r\n \"exportDefaultFrom\",\r\n \"exportNamespaceFrom\",\r\n \"nullishCoalescingOperator\", // ??\r\n \"optionalChaining\", // ?.\r\n \"optionalCatchBinding\",\r\n \"logicalAssignment\", // &&=, ||=, ??=\r\n \"numericSeparator\", // 1_000_000\r\n \"bigInt\",\r\n \"importMeta\", // import.meta\r\n \"topLevelAwait\",\r\n ],\r\n };\r\n\r\n let ast: ReturnType<typeof parse>;\r\n\r\n try {\r\n ast = parse(code, parserOptions);\r\n } catch (parseError) {\r\n // If parsing fails (e.g. unsupported syntax), return the original code\r\n // unchanged rather than crashing. This is safer for production builds.\r\n if (!opts.silent) {\r\n console.warn(\r\n `[console-sniper] Failed to parse code, skipping. Error: ${\r\n parseError instanceof Error ? parseError.message : String(parseError)\r\n }`\r\n );\r\n }\r\n return {\r\n code,\r\n removedCount: 0,\r\n removedMethods: [],\r\n changed: false,\r\n };\r\n }\r\n\r\n // ── Step 2: Traverse & Remove Console Calls ─────────────────\r\n //\r\n // We look for nodes matching this AST pattern:\r\n //\r\n // ExpressionStatement\r\n // └── CallExpression\r\n // ├── callee: MemberExpression\r\n // │ ├── object: Identifier { name: \"console\" }\r\n // │ └── property: Identifier { name: \"log\" | \"warn\" | ... }\r\n // └── arguments: [...]\r\n //\r\n // Matching the full ExpressionStatement (the statement wrapper) rather than\r\n // just the CallExpression lets us safely remove the entire line — including\r\n // the trailing semicolon — without leaving orphan syntax behind.\r\n\r\n traverse(ast, {\r\n ExpressionStatement(nodePath) {\r\n const { expression } = nodePath.node;\r\n\r\n // Must be a function call\r\n if (!t.isCallExpression(expression)) return;\r\n\r\n const { callee } = expression;\r\n\r\n // The callee must be a member expression (object.method)\r\n if (!t.isMemberExpression(callee)) return;\r\n\r\n const { object, property } = callee;\r\n\r\n // The object must be `console` (an Identifier with name \"console\")\r\n if (!t.isIdentifier(object, { name: \"console\" })) return;\r\n\r\n // The property must be one of our configured methods\r\n // Property can be either an Identifier (console.log) or\r\n // a StringLiteral (console[\"log\"]) — we handle both.\r\n let methodName: string | undefined;\r\n\r\n if (t.isIdentifier(property)) {\r\n methodName = property.name;\r\n } else if (t.isStringLiteral(property)) {\r\n methodName = property.value;\r\n }\r\n\r\n if (!methodName || !methodsToRemove.has(methodName)) return;\r\n\r\n // ✅ This is a console call we should remove!\r\n removedMethods.push(methodName);\r\n\r\n // `nodePath.remove()` is the safe Babel way to delete a node.\r\n // It properly handles parent references, scope bindings, etc.\r\n nodePath.remove();\r\n },\r\n });\r\n\r\n // ── Step 3: Remove Console Comments ─────────────────────────\r\n //\r\n // Babel attaches comments to the nearest AST node as `leadingComments`\r\n // or `trailingComments`. We need to scan ALL nodes and filter out\r\n // any comments that mention \"console\".\r\n //\r\n // We do this AFTER the traverse so we don't interfere with node removal.\r\n\r\n if (opts.removeComments) {\r\n removeConsoleComments(ast);\r\n }\r\n\r\n // ── Step 4: Re-generate Source Code ─────────────────────────\r\n //\r\n // @babel/generator walks the (now modified) AST and prints it back to a\r\n // string. We enable `retainLines` to preserve line numbers as much as\r\n // possible — this keeps source maps accurate and diffs readable.\r\n\r\n const { code: transformedCode } = generate(\r\n ast,\r\n {\r\n retainLines: false, // Compact output; set true if you need line parity\r\n compact: false, // Keep human-readable whitespace\r\n concise: false,\r\n comments: true, // Include non-console comments\r\n jsescOption: {\r\n minimal: true, // Don't over-escape unicode\r\n },\r\n },\r\n code // Original source (used for sourcemap generation)\r\n );\r\n\r\n const removedCount = removedMethods.length;\r\n\r\n return {\r\n code: transformedCode,\r\n removedCount,\r\n removedMethods,\r\n changed: removedCount > 0,\r\n };\r\n}\r\n\r\n// Internal Helpers\r\n\r\n/**\r\n * Walk the AST and strip comments that reference \"console\".\r\n *\r\n * Babel stores comments as arrays on AST nodes:\r\n * node.leadingComments — comments before the node\r\n * node.innerComments — comments inside empty blocks\r\n * node.trailingComments — comments after the node\r\n *\r\n * We filter each array, removing comments whose `value` contains the\r\n * CONSOLE_KEYWORD (\"console\").\r\n *\r\n * @param ast - The parsed AST (mutated in place)\r\n */\r\nfunction removeConsoleComments(ast: t.File): void {\r\n /**\r\n * Filter a comment array, removing any that mention \"console\".\r\n */\r\n function filterComments(\r\n comments: t.Comment[] | null | undefined\r\n ): t.Comment[] | null {\r\n if (!comments || comments.length === 0) return comments ?? null;\r\n\r\n return comments.filter(\r\n (comment) => !comment.value.includes(CONSOLE_KEYWORD)\r\n );\r\n }\r\n\r\n // We need to visit every node in the AST.\r\n // `traverse` with no specific node type visits everything.\r\n traverse(ast, {\r\n enter(nodePath) {\r\n const { node } = nodePath;\r\n\r\n // Filter leading comments (e.g. `// console.log here`)\r\n if (node.leadingComments) {\r\n const filtered = filterComments(node.leadingComments);\r\n // Babel uses `null` when there are no comments\r\n (node as t.Node & { leadingComments: t.Comment[] | null }).leadingComments =\r\n filtered;\r\n }\r\n\r\n // Filter trailing comments (e.g. `} // end console block`)\r\n if (node.trailingComments) {\r\n const filtered = filterComments(node.trailingComments);\r\n (node as t.Node & { trailingComments: t.Comment[] | null }).trailingComments =\r\n filtered;\r\n }\r\n\r\n // Filter inner comments (e.g. comments inside empty blocks)\r\n if (node.innerComments) {\r\n const filtered = filterComments(node.innerComments);\r\n (node as t.Node & { innerComments: t.Comment[] | null }).innerComments =\r\n filtered;\r\n }\r\n },\r\n });\r\n}\r\n","/**\r\n * @file constants.ts\r\n * @description Global constants used across the project.\r\n *\r\n * Centralizing magic strings and default values here prevents typos\r\n * and makes it trivial to change defaults in one place.\r\n */\r\n\r\n\r\n// Package Identity\r\n\r\n/** The package name — used in logs, banners, and plugin names. */\r\nexport const PACKAGE_NAME = \"console-sniper\";\r\n\r\n/** Current version — ideally sync'd with package.json at build time. */\r\nexport const PACKAGE_VERSION = \"1.0.0\";\r\n\r\n\r\n// Console Stripping Defaults\r\n\r\n/**\r\n * Default console methods that are removed when `methods` is not specified.\r\n *\r\n * Extending this list is the primary way to support new console variants\r\n * like `console.table`, `console.time`, `console.group`, etc.\r\n */\r\nexport const DEFAULT_METHODS: readonly string[] = [\r\n \"log\",\r\n \"warn\",\r\n \"error\",\r\n \"info\",\r\n \"debug\",\r\n] as const;\r\n\r\n/**\r\n * Whether to remove comments containing \"console\" by default.\r\n */\r\nexport const DEFAULT_REMOVE_COMMENTS = true;\r\n\r\n\r\n// File Extension Filters\r\n\r\n/**\r\n * File extensions that the Babel parser can handle.\r\n * These are used by the CLI file scanner to filter which files to process.\r\n *\r\n * Note: The Babel parser handles TypeScript and JSX natively via plugins.\r\n */\r\nexport const SUPPORTED_EXTENSIONS: readonly string[] = [\r\n \".js\",\r\n \".mjs\",\r\n \".cjs\",\r\n \".jsx\",\r\n \".ts\",\r\n \".tsx\",\r\n \".mts\",\r\n \".cts\",\r\n] as const;\r\n\r\n/**\r\n * Default glob patterns for the CLI to EXCLUDE when scanning directories.\r\n */\r\nexport const DEFAULT_EXCLUDE_PATTERNS: readonly string[] = [\r\n \"**/node_modules/**\",\r\n \"**/dist/**\",\r\n \"**/build/**\",\r\n \"**/.git/**\",\r\n \"**/*.min.js\",\r\n \"**/*.d.ts\",\r\n] as const;\r\n\r\n// AST Parser Config\r\n\r\n/**\r\n * The word we look for when scanning comment text.\r\n * Keeping this as a constant prevents subtle bugs from typos.\r\n */\r\nexport const CONSOLE_KEYWORD = \"console\";\r\n","/**\r\n * @file utils.ts\r\n * @description Pure utility functions shared across the core engine.\r\n */\r\n\r\nimport path from \"node:path\";\r\nimport { SUPPORTED_EXTENSIONS, DEFAULT_METHODS, DEFAULT_REMOVE_COMMENTS } from \"./constants.js\";\r\nimport type { StripConsoleOptions } from \"./types.js\";\r\n\r\nexport function resolveOptionsSync(\r\n options?: StripConsoleOptions\r\n): StripConsoleOptions & {\r\n methods: string[];\r\n removeComments: boolean;\r\n silent: boolean;\r\n} {\r\n return {\r\n methods: options?.methods?.length ? options.methods : [...DEFAULT_METHODS],\r\n removeComments: options?.removeComments ?? DEFAULT_REMOVE_COMMENTS,\r\n include: options?.include ?? [],\r\n exclude: options?.exclude ?? [],\r\n silent: options?.silent ?? false,\r\n };\r\n}\r\n\r\nexport function isSupportedFile(filePath: string): boolean {\r\n const ext = path.extname(filePath).toLowerCase();\r\n return (SUPPORTED_EXTENSIONS as readonly string[]).includes(ext);\r\n}\r\n\r\nexport function shouldProcessFile(\r\n filePath: string,\r\n include: RegExp[] = [],\r\n exclude: RegExp[] = []\r\n): boolean {\r\n const normalized = filePath.replace(/\\\\/g, \"/\");\r\n if (include.length > 0 && !include.some((p) => p.test(normalized))) return false;\r\n if (exclude.length > 0 && exclude.some((p) => p.test(normalized))) return false;\r\n return true;\r\n}\r\n\r\nexport function parseMethodList(input: string): string[] {\r\n return input.split(\",\").map((m) => m.trim()).filter((m) => m.length > 0);\r\n}\r\n\r\nexport function formatCount(count: number, noun: string): string {\r\n return `${count} ${noun}${count !== 1 ? \"s\" : \"\"}`;\r\n}\r\n\r\nexport function isNode(): boolean {\r\n return typeof process !== \"undefined\" && process.versions?.node != null;\r\n}\r\n"],"mappings":"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","names":["path","_traverse","_generate"]}
|