Skip to content

Commit fd30a63

Browse files
committed
JS-1453 Avoid false positives in path-like telemetry filtering
1 parent babb1bb commit fd30a63

File tree

2 files changed

+60
-27
lines changed

2 files changed

+60
-27
lines changed

packages/jsts/src/analysis/projectAnalysis/telemetry.ts

Lines changed: 56 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,8 @@ import { packageJsonStore } from './file-stores/index.js';
2525
import { stripBOM } from '../../rules/helpers/files.js';
2626

2727
const NOT_DETECTED = 'not-detected';
28-
const PATH_COMPILER_OPTIONS = new Set([
29-
'baseUrl',
30-
'declarationDir',
31-
'mapRoot',
32-
'outDir',
33-
'outFile',
34-
'rootDir',
35-
'rootDirs',
36-
'sourceRoot',
37-
'tsBuildInfoFile',
38-
]);
28+
const KNOWN_COMPILER_OPTIONS = buildKnownCompilerOptions();
29+
const PATH_COMPILER_OPTIONS = buildPathCompilerOptions();
3930

4031
let projectAnalysisTelemetryCollector: ProjectAnalysisTelemetryCollector | undefined;
4132

@@ -181,7 +172,7 @@ export class ProjectAnalysisTelemetryCollector {
181172
if (optionName === 'lib') {
182173
return [normalizeLibValue(optionValue)];
183174
}
184-
const sanitized = sanitizeStringOptionValue(optionValue);
175+
const sanitized = sanitizeStringOptionValue(optionName, optionValue);
185176
return sanitized === undefined ? [] : [sanitized];
186177
}
187178

@@ -190,7 +181,7 @@ export class ProjectAnalysisTelemetryCollector {
190181
}
191182

192183
if (typeof optionValue === 'object') {
193-
const sanitized = sanitizeObjectOptionValue(optionValue);
184+
const sanitized = sanitizeObjectOptionValue(optionName, optionValue);
194185
if (sanitized === undefined) {
195186
return [];
196187
}
@@ -248,28 +239,31 @@ function normalizeLibValue(value: string): string {
248239
return match ? match[1] : value;
249240
}
250241

251-
function sanitizeStringOptionValue(value: string): string | undefined {
252-
if (looksLikePath(value)) {
242+
function sanitizeStringOptionValue(optionName: string, value: string): string | undefined {
243+
if (looksLikeFilesystemPath(value)) {
244+
return undefined;
245+
}
246+
if (!KNOWN_COMPILER_OPTIONS.has(optionName) && looksLikePathWithSeparator(value)) {
253247
return undefined;
254248
}
255249
return value;
256250
}
257251

258-
function sanitizeObjectOptionValue(value: unknown): unknown {
252+
function sanitizeObjectOptionValue(optionName: string, value: unknown): unknown {
259253
if (typeof value === 'string') {
260-
return sanitizeStringOptionValue(value);
254+
return sanitizeStringOptionValue(optionName, value);
261255
}
262256
if (Array.isArray(value)) {
263257
const sanitizedValues = value.flatMap(item => {
264-
const sanitized = sanitizeObjectOptionValue(item);
258+
const sanitized = sanitizeObjectOptionValue(optionName, item);
265259
return sanitized === undefined ? [] : [sanitized];
266260
});
267261
return sanitizedValues.length > 0 ? sanitizedValues : undefined;
268262
}
269263
if (value && typeof value === 'object') {
270264
const sanitizedEntries = Object.entries(value as Record<string, unknown>).flatMap(
271265
([key, nested]) => {
272-
const sanitized = sanitizeObjectOptionValue(nested);
266+
const sanitized = sanitizeObjectOptionValue(optionName, nested);
273267
return sanitized === undefined ? [] : [[key, sanitized] as [string, unknown]];
274268
},
275269
);
@@ -278,23 +272,58 @@ function sanitizeObjectOptionValue(value: unknown): unknown {
278272
return value;
279273
}
280274

281-
function looksLikePath(value: string): boolean {
275+
function looksLikeFilesystemPath(value: string): boolean {
282276
return (
283277
value.startsWith('/') ||
284278
/^[a-zA-Z]:[\\/]/.test(value) ||
285279
value.startsWith('\\\\') ||
286280
value.startsWith('file://') ||
287-
value.includes('/') ||
288-
value.includes('\\')
281+
value.startsWith('./') ||
282+
value.startsWith('../') ||
283+
value.startsWith('.\\') ||
284+
value.startsWith('..\\') ||
285+
value.startsWith('~/') ||
286+
value.startsWith('~\\')
289287
);
290288
}
291289

290+
function looksLikePathWithSeparator(value: string): boolean {
291+
return value.includes('/') || value.includes('\\');
292+
}
293+
294+
function buildKnownCompilerOptions(): Set<string> {
295+
return new Set(getTypeScriptOptionDeclarations().map(declaration => declaration.name));
296+
}
297+
298+
function buildPathCompilerOptions(): Set<string> {
299+
const pathOptions = new Set(['paths']);
300+
for (const declaration of getTypeScriptOptionDeclarations()) {
301+
if (
302+
declaration.isFilePath === true ||
303+
(declaration.type === 'list' && declaration.element?.isFilePath === true)
304+
) {
305+
pathOptions.add(declaration.name);
306+
}
307+
}
308+
return pathOptions;
309+
}
310+
311+
type TypeScriptOptionDeclaration = {
312+
name: string;
313+
type: unknown;
314+
isFilePath?: boolean;
315+
element?: {
316+
isFilePath?: boolean;
317+
};
318+
};
319+
320+
function getTypeScriptOptionDeclarations(): TypeScriptOptionDeclaration[] {
321+
return ((ts as unknown as { optionDeclarations?: unknown[] }).optionDeclarations ??
322+
[]) as TypeScriptOptionDeclaration[];
323+
}
324+
292325
function buildEnumOptionValues(): Map<string, Map<number, string>> {
293-
const optionDeclarations = ((ts as unknown as { optionDeclarations?: unknown[] })
294-
.optionDeclarations ?? []) as Array<{
295-
name: string;
296-
type: unknown;
297-
}>;
326+
const optionDeclarations = getTypeScriptOptionDeclarations();
298327
const enums = new Map<string, Map<number, string>>();
299328
for (const declaration of optionDeclarations) {
300329
if (!(declaration.type instanceof Map)) {

packages/jsts/tests/analysis/telemetry.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ describe('project analysis telemetry', () => {
2929
const collector = new ProjectAnalysisTelemetryCollector();
3030
collector.recordCompilerOptions({
3131
lib: ['lib.es2022.d.ts', 'dom'],
32+
jsxImportSource: '@emotion/react',
3233
module: ts.ModuleKind.CommonJS,
3334
strict: true,
35+
types: ['@types/node'],
3436
paths: { '@/*': ['src/*'] },
3537
});
3638
collector.recordCompilerOptions({
@@ -40,9 +42,11 @@ describe('project analysis telemetry', () => {
4042
});
4143

4244
expect(collector.getTelemetry().compilerOptions).toEqual({
45+
jsxImportSource: ['@emotion/react'],
4346
lib: ['dom', 'es2020', 'es2022'],
4447
module: ['commonjs', 'nodenext'],
4548
strict: ['true'],
49+
types: ['@types/node'],
4650
});
4751
});
4852

0 commit comments

Comments
 (0)