Skip to content

Commit 2e4bab3

Browse files
authored
fix: false warning in sveltekit alias (#387)
1 parent af6739f commit 2e4bab3

5 files changed

Lines changed: 70 additions & 8 deletions

File tree

.changeset/three-tools-attack.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'dotenv-diff': patch
3+
---
4+
5+
fixed false warning in sveltekit alias

packages/cli/src/core/scan/patterns.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,31 @@ type Pattern = {
44
name: EnvPatternName;
55
regex: RegExp;
66
processor?: (match: RegExpExecArray) => string[];
7+
/** When set, overrides the file-level imports list for usages from this pattern */
8+
sourceModule?: string;
79
};
810

911
/**
1012
* Builds SvelteKit env patterns for an aliased import.
1113
* Handles: import { env as aliasName } from '$env/dynamic/private'
1214
* @param alias - The local alias used for the env object (e.g. "privateEnv")
15+
* @param sourceModule - The specific $env module the alias was imported from
1316
* @returns Patterns matching aliasName.VAR and { VAR } = aliasName
1417
*/
15-
export function buildSveltekitAliasPatterns(alias: string): Pattern[] {
18+
export function buildSveltekitAliasPatterns(
19+
alias: string,
20+
sourceModule: string,
21+
): Pattern[] {
1622
return [
1723
{
1824
name: 'sveltekit' as const,
1925
regex: new RegExp(`(?<![.\\w])${alias}\\.([A-Z_][A-Z0-9_]*)`, 'g'),
26+
sourceModule,
2027
},
2128
{
2229
name: 'sveltekit' as const,
2330
regex: new RegExp(`\\{([^}]*)\\}\\s*=\\s*${alias}\\b`, 'g'),
31+
sourceModule,
2432
processor: (match) => {
2533
const content = match[1];
2634
if (!content) return [];

packages/cli/src/core/scan/scanFile.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,20 @@ export function scanFile(
3636
}
3737

3838
// Detect aliased $env imports: import { env as aliasName } from '$env/dynamic/private'
39+
// Capture both the alias name (group 1) and the source module (group 2)
3940
const aliasImportRegex =
40-
/import\s*\{\s*env\s+as\s+(\w+)\s*\}\s*from\s*['"]\$env\/(?:static|dynamic)\/(?:private|public)['"]/g;
41+
/import\s*\{\s*env\s+as\s+(\w+)\s*\}\s*from\s*['"](\$env\/(?:static|dynamic)\/(?:private|public))['"]/g;
4142

4243
const allPatterns = [...ENV_PATTERNS];
4344
let aliasImportMatch: RegExpExecArray | null;
4445

4546
while ((aliasImportMatch = aliasImportRegex.exec(content)) !== null) {
46-
allPatterns.push(...buildSveltekitAliasPatterns(aliasImportMatch[1]!));
47+
allPatterns.push(
48+
...buildSveltekitAliasPatterns(
49+
aliasImportMatch[1]!,
50+
aliasImportMatch[2]!,
51+
),
52+
);
4753
}
4854

4955
for (const pattern of allPatterns) {
@@ -97,7 +103,7 @@ export function scanFile(
97103
line: lineNumber,
98104
column,
99105
pattern: pattern.name,
100-
imports: envImports,
106+
imports: pattern.sourceModule ? [pattern.sourceModule] : envImports,
101107
context: contextLine,
102108
isLogged,
103109
});

packages/cli/test/unit/core/scan/patterns.test.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -468,27 +468,48 @@ describe('scanFile - Pattern Detection', () => {
468468
}
469469

470470
it('returns two patterns for a given alias', () => {
471-
const patterns = buildSveltekitAliasPatterns('privateEnv');
471+
const patterns = buildSveltekitAliasPatterns(
472+
'privateEnv',
473+
'$env/dynamic/private',
474+
);
472475
expect(patterns).toHaveLength(2);
473476
expect(patterns[0]?.regex).toBeInstanceOf(RegExp);
474477
expect(patterns[1]?.regex).toBeInstanceOf(RegExp);
475478
expect(patterns.every((p) => p.name === 'sveltekit')).toBe(true);
476479
});
477480

481+
it('sets sourceModule on both returned patterns', () => {
482+
const patterns = buildSveltekitAliasPatterns(
483+
'privateEnv',
484+
'$env/dynamic/private',
485+
);
486+
expect(patterns[0]?.sourceModule).toBe('$env/dynamic/private');
487+
expect(patterns[1]?.sourceModule).toBe('$env/dynamic/private');
488+
});
489+
478490
it('dot notation pattern matches alias.VAR', () => {
479-
const [dotPattern] = buildSveltekitAliasPatterns('privateEnv');
491+
const [dotPattern] = buildSveltekitAliasPatterns(
492+
'privateEnv',
493+
'$env/dynamic/private',
494+
);
480495
expect(dotPattern!.regex.test('privateEnv.MY_SECRET')).toBe(true);
481496
expect(dotPattern!.regex.test('env.MY_SECRET')).toBe(false);
482497
});
483498

484499
it('destructuring processor returns [] for empty content (if !content branch)', () => {
485-
const [, destructurePattern] = buildSveltekitAliasPatterns('privateEnv');
500+
const [, destructurePattern] = buildSveltekitAliasPatterns(
501+
'privateEnv',
502+
'$env/dynamic/private',
503+
);
486504
const result = destructurePattern!.processor!(makeMatch('{}', ''));
487505
expect(result).toEqual([]);
488506
});
489507

490508
it('destructuring processor returns [] when content is undefined', () => {
491-
const [, destructurePattern] = buildSveltekitAliasPatterns('privateEnv');
509+
const [, destructurePattern] = buildSveltekitAliasPatterns(
510+
'privateEnv',
511+
'$env/dynamic/private',
512+
);
492513
const result = destructurePattern!.processor!(makeMatch('{}', undefined));
493514
expect(result).toEqual([]);
494515
});

packages/cli/test/unit/core/scan/scanFile.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,28 @@ const key = privateEnv.SUPABASE_SERVICE_ROLE_KEY;`;
200200
);
201201
});
202202

203+
it('sets imports to only the specific module for each aliased usage (prevents false framework warnings)', () => {
204+
const content = `import { env as publicEnv } from '$env/dynamic/public';
205+
import { env as privateEnv } from '$env/dynamic/private';
206+
const url = publicEnv.PUBLIC_SUPABASE_URL;
207+
const key = privateEnv.SUPABASE_SERVICE_ROLE_KEY;`;
208+
const usages = scanFile(
209+
'/test/project/src/lib/supabase.ts',
210+
content,
211+
baseOpts,
212+
);
213+
214+
const publicUsage = usages.find(
215+
(u) => u.variable === 'PUBLIC_SUPABASE_URL',
216+
);
217+
const privateUsage = usages.find(
218+
(u) => u.variable === 'SUPABASE_SERVICE_ROLE_KEY',
219+
);
220+
221+
expect(publicUsage?.imports).toEqual(['$env/dynamic/public']);
222+
expect(privateUsage?.imports).toEqual(['$env/dynamic/private']);
223+
});
224+
203225
it('detects env variables via destructuring from aliased import', () => {
204226
const content = `import { env as privateEnv } from '$env/dynamic/private';
205227
const { SECRET_KEY, API_TOKEN } = privateEnv;`;

0 commit comments

Comments
 (0)