envprobe 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 +316 -0
- package/bin/envcheck.js +68 -0
- package/package.json +49 -0
- package/src/analyzer.js +179 -0
- package/src/autocomplete.js +135 -0
- package/src/cache.js +114 -0
- package/src/cli.js +606 -0
- package/src/config.js +118 -0
- package/src/formatters/github.js +164 -0
- package/src/formatters/json.js +114 -0
- package/src/formatters/table.js +92 -0
- package/src/formatters/text.js +198 -0
- package/src/ignore.js +313 -0
- package/src/parser.js +119 -0
- package/src/plugins.js +138 -0
- package/src/progress.js +181 -0
- package/src/repl.js +416 -0
- package/src/scanner.js +182 -0
- package/src/scanners/go.js +89 -0
- package/src/scanners/javascript.js +93 -0
- package/src/scanners/python.js +97 -0
- package/src/scanners/ruby.js +90 -0
- package/src/scanners/rust.js +103 -0
- package/src/scanners/shell.js +125 -0
- package/src/security.js +411 -0
- package/src/suggestions.js +154 -0
- package/src/utils.js +57 -0
- package/src/watch.js +131 -0
package/src/scanner.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Scanner Module
|
|
3
|
+
*
|
|
4
|
+
* Recursively scans directories to find source files and detect environment variable references.
|
|
5
|
+
* Handles symlinks, permission errors, and integrates with ignore patterns.
|
|
6
|
+
*
|
|
7
|
+
* Requirements: 1.4.1-1.4.5
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import { shouldIgnore } from './ignore.js';
|
|
13
|
+
import { normalizePath } from './utils.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get supported file extensions for scanning
|
|
17
|
+
*
|
|
18
|
+
* @returns {string[]} Array of file extensions to scan
|
|
19
|
+
*
|
|
20
|
+
* Requirements: 1.4.2
|
|
21
|
+
*/
|
|
22
|
+
export function getSupportedExtensions() {
|
|
23
|
+
return [
|
|
24
|
+
'.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', // JavaScript/TypeScript
|
|
25
|
+
'.py', // Python
|
|
26
|
+
'.go', // Go
|
|
27
|
+
'.rb', // Ruby
|
|
28
|
+
'.rs', // Rust
|
|
29
|
+
'.sh', '.bash', '.zsh' // Shell/Bash
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Check if a file has a supported extension
|
|
35
|
+
*
|
|
36
|
+
* @param {string} filePath - Path to the file
|
|
37
|
+
* @returns {boolean} True if file extension is supported
|
|
38
|
+
*
|
|
39
|
+
* Requirements: 1.4.2
|
|
40
|
+
*/
|
|
41
|
+
export function hasSupportedExtension(filePath) {
|
|
42
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
43
|
+
return getSupportedExtensions().includes(ext);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Scan a directory recursively for source files
|
|
48
|
+
*
|
|
49
|
+
* @param {string} dirPath - Directory path to scan
|
|
50
|
+
* @param {string[]} ignorePatterns - Array of ignore patterns
|
|
51
|
+
* @param {Set<string>} visitedPaths - Set of visited paths (for symlink cycle detection)
|
|
52
|
+
* @returns {Promise<string[]>} Array of file paths to scan
|
|
53
|
+
*
|
|
54
|
+
* Preconditions:
|
|
55
|
+
* - dirPath is a valid directory path
|
|
56
|
+
* - ignorePatterns is a valid array (may be empty)
|
|
57
|
+
*
|
|
58
|
+
* Postconditions:
|
|
59
|
+
* - Returns array of all source files found
|
|
60
|
+
* - Excludes files matching ignore patterns
|
|
61
|
+
* - Handles symlink cycles without infinite loops
|
|
62
|
+
* - Continues on permission errors
|
|
63
|
+
*
|
|
64
|
+
* Requirements: 1.4.1, 1.4.3, 1.4.4, 1.4.5
|
|
65
|
+
*/
|
|
66
|
+
export async function scanDirectory(dirPath, ignorePatterns = [], visitedPaths = new Set()) {
|
|
67
|
+
const files = [];
|
|
68
|
+
const normalizedDirPath = normalizePath(path.resolve(dirPath));
|
|
69
|
+
|
|
70
|
+
// Check if we've already visited this path (symlink cycle detection)
|
|
71
|
+
if (visitedPaths.has(normalizedDirPath)) {
|
|
72
|
+
return files;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
visitedPaths.add(normalizedDirPath);
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
|
|
79
|
+
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
82
|
+
const relativePath = normalizePath(path.relative(process.cwd(), fullPath));
|
|
83
|
+
|
|
84
|
+
// Check if path should be ignored (check both full path and just the name)
|
|
85
|
+
if (shouldIgnore(relativePath, ignorePatterns) ||
|
|
86
|
+
shouldIgnore(entry.name, ignorePatterns) ||
|
|
87
|
+
shouldIgnore(relativePath + '/', ignorePatterns)) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
let stats;
|
|
93
|
+
|
|
94
|
+
if (entry.isSymbolicLink()) {
|
|
95
|
+
// For symlinks, get stats of the target
|
|
96
|
+
try {
|
|
97
|
+
stats = await fs.promises.stat(fullPath);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
// Broken symlink or permission denied - skip it
|
|
100
|
+
console.warn(`Warning: Cannot access symlink target: ${relativePath}`);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
stats = entry;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (stats.isDirectory()) {
|
|
108
|
+
// Recursively scan subdirectory
|
|
109
|
+
const subFiles = await scanDirectory(fullPath, ignorePatterns, visitedPaths);
|
|
110
|
+
files.push(...subFiles);
|
|
111
|
+
} else if (stats.isFile() && hasSupportedExtension(fullPath)) {
|
|
112
|
+
// Add file to list if it has a supported extension
|
|
113
|
+
files.push(fullPath);
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
// Permission denied or other error - log warning and continue
|
|
117
|
+
if (error.code === 'EACCES' || error.code === 'EPERM') {
|
|
118
|
+
console.warn(`Warning: Permission denied: ${relativePath}`);
|
|
119
|
+
} else {
|
|
120
|
+
console.warn(`Warning: Error accessing ${relativePath}: ${error.message}`);
|
|
121
|
+
}
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} catch (error) {
|
|
126
|
+
// Directory read error - log warning and return what we have
|
|
127
|
+
if (error.code === 'EACCES' || error.code === 'EPERM') {
|
|
128
|
+
console.warn(`Warning: Permission denied: ${normalizedDirPath}`);
|
|
129
|
+
} else if (error.code === 'ENOENT') {
|
|
130
|
+
console.warn(`Warning: Directory not found: ${normalizedDirPath}`);
|
|
131
|
+
} else {
|
|
132
|
+
console.warn(`Warning: Error reading directory ${normalizedDirPath}: ${error.message}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return files;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Scan a single file or directory
|
|
141
|
+
*
|
|
142
|
+
* @param {string} targetPath - File or directory path to scan
|
|
143
|
+
* @param {string[]} ignorePatterns - Array of ignore patterns
|
|
144
|
+
* @returns {Promise<string[]>} Array of file paths to scan
|
|
145
|
+
*
|
|
146
|
+
* Preconditions:
|
|
147
|
+
* - targetPath exists and is readable
|
|
148
|
+
*
|
|
149
|
+
* Postconditions:
|
|
150
|
+
* - Returns array with single file if targetPath is a file
|
|
151
|
+
* - Returns array of all source files if targetPath is a directory
|
|
152
|
+
*
|
|
153
|
+
* Requirements: 1.4.1, 1.5.1
|
|
154
|
+
*/
|
|
155
|
+
export async function scan(targetPath, ignorePatterns = []) {
|
|
156
|
+
try {
|
|
157
|
+
const stats = await fs.promises.stat(targetPath);
|
|
158
|
+
|
|
159
|
+
if (stats.isFile()) {
|
|
160
|
+
// Single file - return it if it has a supported extension
|
|
161
|
+
if (hasSupportedExtension(targetPath)) {
|
|
162
|
+
return [targetPath];
|
|
163
|
+
} else {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
} else if (stats.isDirectory()) {
|
|
167
|
+
// Directory - scan recursively
|
|
168
|
+
return await scanDirectory(targetPath, ignorePatterns);
|
|
169
|
+
} else {
|
|
170
|
+
console.warn(`Warning: ${targetPath} is neither a file nor a directory`);
|
|
171
|
+
return [];
|
|
172
|
+
}
|
|
173
|
+
} catch (error) {
|
|
174
|
+
if (error.code === 'ENOENT') {
|
|
175
|
+
throw new Error(`Path not found: ${targetPath}`);
|
|
176
|
+
} else if (error.code === 'EACCES' || error.code === 'EPERM') {
|
|
177
|
+
throw new Error(`Permission denied: ${targetPath}`);
|
|
178
|
+
} else {
|
|
179
|
+
throw new Error(`Error accessing ${targetPath}: ${error.message}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Go Scanner
|
|
3
|
+
* Detects environment variable references in Go files
|
|
4
|
+
* Supports: os.Getenv("VAR"), os.LookupEnv("VAR")
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Regex patterns for detecting environment variable references
|
|
9
|
+
* Pattern 1: os.Getenv("VAR_NAME") - standard library function for getting env vars
|
|
10
|
+
* Pattern 2: os.LookupEnv("VAR_NAME") - returns value and boolean indicating if var exists
|
|
11
|
+
*/
|
|
12
|
+
const PATTERNS = [
|
|
13
|
+
// os.Getenv("VAR_NAME") - standard way to get environment variables
|
|
14
|
+
/os\.Getenv\("([A-Z_][A-Z0-9_]*)"\)/g,
|
|
15
|
+
|
|
16
|
+
// os.LookupEnv("VAR_NAME") - returns (value, exists) tuple
|
|
17
|
+
/os\.LookupEnv\("([A-Z_][A-Z0-9_]*)"\)/g,
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Supported file extensions for Go
|
|
22
|
+
*/
|
|
23
|
+
const SUPPORTED_EXTENSIONS = ['.go'];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Scan file content for environment variable references
|
|
27
|
+
* @param {string} content - File content to scan
|
|
28
|
+
* @param {string} filePath - Path to the file being scanned
|
|
29
|
+
* @returns {Array<{varName: string, filePath: string, lineNumber: number, pattern: string}>}
|
|
30
|
+
*/
|
|
31
|
+
export function scan(content, filePath) {
|
|
32
|
+
const references = [];
|
|
33
|
+
const lines = content.split('\n');
|
|
34
|
+
|
|
35
|
+
for (let i = 0; i < lines.length; i++) {
|
|
36
|
+
references.push(...scanLine(lines[i], filePath, i + 1));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return references;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function scanLine(line, filePath, lineNumber) {
|
|
43
|
+
const references = [];
|
|
44
|
+
|
|
45
|
+
for (const pattern of PATTERNS) {
|
|
46
|
+
pattern.lastIndex = 0;
|
|
47
|
+
let match;
|
|
48
|
+
while ((match = pattern.exec(line)) !== null) {
|
|
49
|
+
const varName = match[1];
|
|
50
|
+
|
|
51
|
+
if (varName && isValidEnvVarName(varName)) {
|
|
52
|
+
references.push({
|
|
53
|
+
varName,
|
|
54
|
+
filePath,
|
|
55
|
+
lineNumber,
|
|
56
|
+
pattern: match[0],
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return references;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Validate environment variable name
|
|
67
|
+
* Must start with letter or underscore, contain only uppercase letters, digits, and underscores
|
|
68
|
+
* @param {string} varName - Variable name to validate
|
|
69
|
+
* @returns {boolean}
|
|
70
|
+
*/
|
|
71
|
+
function isValidEnvVarName(varName) {
|
|
72
|
+
return /^[A-Z_][A-Z0-9_]*$/.test(varName);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get supported file extensions
|
|
77
|
+
* @returns {string[]}
|
|
78
|
+
*/
|
|
79
|
+
export function getSupportedExtensions() {
|
|
80
|
+
return SUPPORTED_EXTENSIONS;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get regex patterns used for scanning
|
|
85
|
+
* @returns {RegExp[]}
|
|
86
|
+
*/
|
|
87
|
+
export function getPatterns() {
|
|
88
|
+
return PATTERNS;
|
|
89
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JavaScript/TypeScript Scanner
|
|
3
|
+
* Detects environment variable references in JS/TS files
|
|
4
|
+
* Supports: process.env.VAR, process.env['VAR'], import.meta.env.VAR
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Regex patterns for detecting environment variable references
|
|
9
|
+
* Pattern 1: process.env.VAR_NAME (dot notation)
|
|
10
|
+
* Pattern 2: process.env['VAR_NAME'] or process.env["VAR_NAME"] (bracket notation)
|
|
11
|
+
* Pattern 3: import.meta.env.VAR_NAME (Vite/modern bundlers)
|
|
12
|
+
*/
|
|
13
|
+
const PATTERNS = [
|
|
14
|
+
// Dot notation: process.env.VAR_NAME
|
|
15
|
+
/process\.env\.([A-Z_][A-Z0-9_]*)/g,
|
|
16
|
+
|
|
17
|
+
// Bracket notation: process.env['VAR_NAME'] or process.env["VAR_NAME"]
|
|
18
|
+
/process\.env\[['"]([A-Z_][A-Z0-9_]*)['"]]/g,
|
|
19
|
+
|
|
20
|
+
// Vite/modern bundlers: import.meta.env.VAR_NAME
|
|
21
|
+
/import\.meta\.env\.([A-Z_][A-Z0-9_]*)/g,
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Supported file extensions for JavaScript/TypeScript
|
|
26
|
+
*/
|
|
27
|
+
const SUPPORTED_EXTENSIONS = ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs'];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Scan file content for environment variable references
|
|
31
|
+
* @param {string} content - File content to scan
|
|
32
|
+
* @param {string} filePath - Path to the file being scanned
|
|
33
|
+
* @returns {Array<{varName: string, filePath: string, lineNumber: number, pattern: string}>}
|
|
34
|
+
*/
|
|
35
|
+
export function scan(content, filePath) {
|
|
36
|
+
const references = [];
|
|
37
|
+
const lines = content.split('\n');
|
|
38
|
+
|
|
39
|
+
for (let i = 0; i < lines.length; i++) {
|
|
40
|
+
references.push(...scanLine(lines[i], filePath, i + 1));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return references;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function scanLine(line, filePath, lineNumber) {
|
|
47
|
+
const references = [];
|
|
48
|
+
|
|
49
|
+
for (const pattern of PATTERNS) {
|
|
50
|
+
pattern.lastIndex = 0;
|
|
51
|
+
let match;
|
|
52
|
+
while ((match = pattern.exec(line)) !== null) {
|
|
53
|
+
const varName = match[1];
|
|
54
|
+
|
|
55
|
+
if (varName && isValidEnvVarName(varName)) {
|
|
56
|
+
references.push({
|
|
57
|
+
varName,
|
|
58
|
+
filePath,
|
|
59
|
+
lineNumber,
|
|
60
|
+
pattern: match[0],
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return references;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Validate environment variable name
|
|
71
|
+
* Must start with letter or underscore, contain only uppercase letters, digits, and underscores
|
|
72
|
+
* @param {string} varName - Variable name to validate
|
|
73
|
+
* @returns {boolean}
|
|
74
|
+
*/
|
|
75
|
+
function isValidEnvVarName(varName) {
|
|
76
|
+
return /^[A-Z_][A-Z0-9_]*$/.test(varName);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get supported file extensions
|
|
81
|
+
* @returns {string[]}
|
|
82
|
+
*/
|
|
83
|
+
export function getSupportedExtensions() {
|
|
84
|
+
return SUPPORTED_EXTENSIONS;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get regex patterns used for scanning
|
|
89
|
+
* @returns {RegExp[]}
|
|
90
|
+
*/
|
|
91
|
+
export function getPatterns() {
|
|
92
|
+
return PATTERNS;
|
|
93
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python Scanner
|
|
3
|
+
* Detects environment variable references in Python files
|
|
4
|
+
* Supports: os.environ['VAR'], os.environ.get('VAR'), os.getenv('VAR')
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Regex patterns for detecting environment variable references
|
|
9
|
+
* Pattern 1: os.environ['VAR_NAME'] or os.environ["VAR_NAME"] (bracket notation)
|
|
10
|
+
* Pattern 2: os.environ.get('VAR_NAME') or os.environ.get("VAR_NAME") (get method)
|
|
11
|
+
* Pattern 3: os.getenv('VAR_NAME') or os.getenv("VAR_NAME") (getenv function)
|
|
12
|
+
*/
|
|
13
|
+
const PATTERNS = [
|
|
14
|
+
// Bracket notation: os.environ['VAR_NAME'] or os.environ["VAR_NAME"]
|
|
15
|
+
/os\.environ\[['"]([A-Z_][A-Z0-9_]*)['"]]/g,
|
|
16
|
+
|
|
17
|
+
// Get method: os.environ.get('VAR_NAME') or os.environ.get("VAR_NAME")
|
|
18
|
+
// Matches with or without default values: os.environ.get('VAR', 'default')
|
|
19
|
+
// The pattern captures up to and including the closing paren if no comma follows
|
|
20
|
+
/os\.environ\.get\(['"]([A-Z_][A-Z0-9_]*)['"](?:,.*?)?\)/g,
|
|
21
|
+
|
|
22
|
+
// Getenv function: os.getenv('VAR_NAME') or os.getenv("VAR_NAME")
|
|
23
|
+
// Matches with or without default values: os.getenv('VAR', 'default')
|
|
24
|
+
// The pattern captures up to and including the closing paren if no comma follows
|
|
25
|
+
/os\.getenv\(['"]([A-Z_][A-Z0-9_]*)['"](?:,.*?)?\)/g,
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Supported file extensions for Python
|
|
30
|
+
*/
|
|
31
|
+
const SUPPORTED_EXTENSIONS = ['.py'];
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Scan file content for environment variable references
|
|
35
|
+
* @param {string} content - File content to scan
|
|
36
|
+
* @param {string} filePath - Path to the file being scanned
|
|
37
|
+
* @returns {Array<{varName: string, filePath: string, lineNumber: number, pattern: string}>}
|
|
38
|
+
*/
|
|
39
|
+
export function scan(content, filePath) {
|
|
40
|
+
const references = [];
|
|
41
|
+
const lines = content.split('\n');
|
|
42
|
+
|
|
43
|
+
for (let i = 0; i < lines.length; i++) {
|
|
44
|
+
references.push(...scanLine(lines[i], filePath, i + 1));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return references;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function scanLine(line, filePath, lineNumber) {
|
|
51
|
+
const references = [];
|
|
52
|
+
|
|
53
|
+
for (const pattern of PATTERNS) {
|
|
54
|
+
pattern.lastIndex = 0;
|
|
55
|
+
let match;
|
|
56
|
+
while ((match = pattern.exec(line)) !== null) {
|
|
57
|
+
const varName = match[1];
|
|
58
|
+
|
|
59
|
+
if (varName && isValidEnvVarName(varName)) {
|
|
60
|
+
references.push({
|
|
61
|
+
varName,
|
|
62
|
+
filePath,
|
|
63
|
+
lineNumber,
|
|
64
|
+
pattern: match[0],
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return references;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Validate environment variable name
|
|
75
|
+
* Must start with letter or underscore, contain only uppercase letters, digits, and underscores
|
|
76
|
+
* @param {string} varName - Variable name to validate
|
|
77
|
+
* @returns {boolean}
|
|
78
|
+
*/
|
|
79
|
+
function isValidEnvVarName(varName) {
|
|
80
|
+
return /^[A-Z_][A-Z0-9_]*$/.test(varName);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get supported file extensions
|
|
85
|
+
* @returns {string[]}
|
|
86
|
+
*/
|
|
87
|
+
export function getSupportedExtensions() {
|
|
88
|
+
return SUPPORTED_EXTENSIONS;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get regex patterns used for scanning
|
|
93
|
+
* @returns {RegExp[]}
|
|
94
|
+
*/
|
|
95
|
+
export function getPatterns() {
|
|
96
|
+
return PATTERNS;
|
|
97
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ruby Scanner
|
|
3
|
+
* Detects environment variable references in Ruby files
|
|
4
|
+
* Supports: ENV['VAR'], ENV.fetch('VAR')
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Regex patterns for detecting environment variable references
|
|
9
|
+
* Pattern 1: ENV['VAR_NAME'] or ENV["VAR_NAME"] (bracket notation)
|
|
10
|
+
* Pattern 2: ENV.fetch('VAR_NAME') or ENV.fetch("VAR_NAME") (fetch method)
|
|
11
|
+
*/
|
|
12
|
+
const PATTERNS = [
|
|
13
|
+
// Bracket notation: ENV['VAR_NAME'] or ENV["VAR_NAME"]
|
|
14
|
+
/ENV\[['"]([A-Z_][A-Z0-9_]*)['"]]/g,
|
|
15
|
+
|
|
16
|
+
// Fetch method: ENV.fetch('VAR_NAME') or ENV.fetch("VAR_NAME")
|
|
17
|
+
// Matches with or without default values: ENV.fetch('VAR', 'default')
|
|
18
|
+
/ENV\.fetch\(['"]([A-Z_][A-Z0-9_]*)['"](?:,.*?)?\)/g,
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Supported file extensions for Ruby
|
|
23
|
+
*/
|
|
24
|
+
const SUPPORTED_EXTENSIONS = ['.rb'];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Scan file content for environment variable references
|
|
28
|
+
* @param {string} content - File content to scan
|
|
29
|
+
* @param {string} filePath - Path to the file being scanned
|
|
30
|
+
* @returns {Array<{varName: string, filePath: string, lineNumber: number, pattern: string}>}
|
|
31
|
+
*/
|
|
32
|
+
export function scan(content, filePath) {
|
|
33
|
+
const references = [];
|
|
34
|
+
const lines = content.split('\n');
|
|
35
|
+
|
|
36
|
+
for (let i = 0; i < lines.length; i++) {
|
|
37
|
+
references.push(...scanLine(lines[i], filePath, i + 1));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return references;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function scanLine(line, filePath, lineNumber) {
|
|
44
|
+
const references = [];
|
|
45
|
+
|
|
46
|
+
for (const pattern of PATTERNS) {
|
|
47
|
+
pattern.lastIndex = 0;
|
|
48
|
+
let match;
|
|
49
|
+
while ((match = pattern.exec(line)) !== null) {
|
|
50
|
+
const varName = match[1];
|
|
51
|
+
|
|
52
|
+
if (varName && isValidEnvVarName(varName)) {
|
|
53
|
+
references.push({
|
|
54
|
+
varName,
|
|
55
|
+
filePath,
|
|
56
|
+
lineNumber,
|
|
57
|
+
pattern: match[0],
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return references;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Validate environment variable name
|
|
68
|
+
* Must start with letter or underscore, contain only uppercase letters, digits, and underscores
|
|
69
|
+
* @param {string} varName - Variable name to validate
|
|
70
|
+
* @returns {boolean}
|
|
71
|
+
*/
|
|
72
|
+
function isValidEnvVarName(varName) {
|
|
73
|
+
return /^[A-Z_][A-Z0-9_]*$/.test(varName);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get supported file extensions
|
|
78
|
+
* @returns {string[]}
|
|
79
|
+
*/
|
|
80
|
+
export function getSupportedExtensions() {
|
|
81
|
+
return SUPPORTED_EXTENSIONS;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get regex patterns used for scanning
|
|
86
|
+
* @returns {RegExp[]}
|
|
87
|
+
*/
|
|
88
|
+
export function getPatterns() {
|
|
89
|
+
return PATTERNS;
|
|
90
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rust Scanner
|
|
3
|
+
* Detects environment variable references in Rust files
|
|
4
|
+
* Supports: env::var("VAR"), std::env::var("VAR"), env::var_os("VAR"), std::env::var_os("VAR")
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Regex patterns for detecting environment variable references
|
|
9
|
+
* Pattern 1: std::env::var("VAR_NAME") - full path form (returns Result<String>)
|
|
10
|
+
* Pattern 2: std::env::var_os("VAR_NAME") - full path form returning OsString (returns Option<OsString>)
|
|
11
|
+
* Pattern 3: env::var("VAR_NAME") - short form for getting env vars (returns Result<String>)
|
|
12
|
+
* Pattern 4: env::var_os("VAR_NAME") - short form returning OsString (returns Option<OsString>)
|
|
13
|
+
*
|
|
14
|
+
* Note: Order matters! We check std::env:: patterns first to avoid double-matching
|
|
15
|
+
* (since "env::var" would match within "std::env::var")
|
|
16
|
+
*/
|
|
17
|
+
const PATTERNS = [
|
|
18
|
+
// std::env::var("VAR_NAME") - full path form, returns Result<String, VarError>
|
|
19
|
+
// Use negative lookbehind to ensure we don't match if preceded by non-whitespace (avoids matching "std::env::var")
|
|
20
|
+
/std::env::var\("([A-Z_][A-Z0-9_]*)"\)/g,
|
|
21
|
+
|
|
22
|
+
// std::env::var_os("VAR_NAME") - full path form, returns Option<OsString>
|
|
23
|
+
/std::env::var_os\("([A-Z_][A-Z0-9_]*)"\)/g,
|
|
24
|
+
|
|
25
|
+
// env::var("VAR_NAME") - short form, returns Result<String, VarError>
|
|
26
|
+
// Use negative lookbehind to ensure not preceded by "std::" to avoid double-matching
|
|
27
|
+
/(?<!std::)env::var\("([A-Z_][A-Z0-9_]*)"\)/g,
|
|
28
|
+
|
|
29
|
+
// env::var_os("VAR_NAME") - short form, returns Option<OsString>
|
|
30
|
+
// Use negative lookbehind to ensure not preceded by "std::" to avoid double-matching
|
|
31
|
+
/(?<!std::)env::var_os\("([A-Z_][A-Z0-9_]*)"\)/g,
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Supported file extensions for Rust
|
|
36
|
+
*/
|
|
37
|
+
const SUPPORTED_EXTENSIONS = ['.rs'];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Scan file content for environment variable references
|
|
41
|
+
* @param {string} content - File content to scan
|
|
42
|
+
* @param {string} filePath - Path to the file being scanned
|
|
43
|
+
* @returns {Array<{varName: string, filePath: string, lineNumber: number, pattern: string}>}
|
|
44
|
+
*/
|
|
45
|
+
export function scan(content, filePath) {
|
|
46
|
+
const references = [];
|
|
47
|
+
const lines = content.split('\n');
|
|
48
|
+
|
|
49
|
+
for (let i = 0; i < lines.length; i++) {
|
|
50
|
+
references.push(...scanLine(lines[i], filePath, i + 1));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return references;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function scanLine(line, filePath, lineNumber) {
|
|
57
|
+
const references = [];
|
|
58
|
+
|
|
59
|
+
for (const pattern of PATTERNS) {
|
|
60
|
+
pattern.lastIndex = 0;
|
|
61
|
+
let match;
|
|
62
|
+
while ((match = pattern.exec(line)) !== null) {
|
|
63
|
+
const varName = match[1];
|
|
64
|
+
|
|
65
|
+
if (varName && isValidEnvVarName(varName)) {
|
|
66
|
+
references.push({
|
|
67
|
+
varName,
|
|
68
|
+
filePath,
|
|
69
|
+
lineNumber,
|
|
70
|
+
pattern: match[0],
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return references;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Validate environment variable name
|
|
81
|
+
* Must start with letter or underscore, contain only uppercase letters, digits, and underscores
|
|
82
|
+
* @param {string} varName - Variable name to validate
|
|
83
|
+
* @returns {boolean}
|
|
84
|
+
*/
|
|
85
|
+
function isValidEnvVarName(varName) {
|
|
86
|
+
return /^[A-Z_][A-Z0-9_]*$/.test(varName);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get supported file extensions
|
|
91
|
+
* @returns {string[]}
|
|
92
|
+
*/
|
|
93
|
+
export function getSupportedExtensions() {
|
|
94
|
+
return SUPPORTED_EXTENSIONS;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get regex patterns used for scanning
|
|
99
|
+
* @returns {RegExp[]}
|
|
100
|
+
*/
|
|
101
|
+
export function getPatterns() {
|
|
102
|
+
return PATTERNS;
|
|
103
|
+
}
|