Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions src/analyze/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

const path = require('path');
const ts = require('typescript');

const { parseCustomFunctionSignature } = require('./utils/customFunctionParser');
const { getAllFiles } = require('../utils/fileProcessor');
const { analyzeJsFile } = require('./javascript');
Expand All @@ -19,11 +19,6 @@ async function analyzeDirectory(dirPath, customFunctions) {
const customFunctionSignatures = (customFunctions && customFunctions?.length > 0) ? customFunctions.map(parseCustomFunctionSignature) : null;

const files = getAllFiles(dirPath);
const tsFiles = files.filter(file => /\.(tsx?)$/.test(file));
const tsProgram = ts.createProgram(tsFiles, {
target: ts.ScriptTarget.ESNext,
module: ts.ModuleKind.CommonJS,
});

for (const file of files) {
let events = [];
Expand All @@ -37,7 +32,8 @@ async function analyzeDirectory(dirPath, customFunctions) {
if (isJsFile) {
events = analyzeJsFile(file, customFunctionSignatures);
} else if (isTsFile) {
events = analyzeTsFile(file, tsProgram, customFunctionSignatures);
// Pass null program so analyzeTsFile will create a per-file program using the file's nearest tsconfig.json
events = analyzeTsFile(file, null, customFunctionSignatures);
} else if (isPythonFile) {
events = await analyzePythonFile(file, customFunctionSignatures);
} else if (isRubyFile) {
Expand Down
30 changes: 26 additions & 4 deletions src/analyze/typescript/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const ts = require('typescript');
const { detectAnalyticsSource } = require('./detectors');
const { extractEventData, processEventData } = require('./extractors');
const { findWrappingFunction } = require('./utils/function-finder');
const path = require('path');

/**
* Error thrown when TypeScript program cannot be created
Expand Down Expand Up @@ -44,16 +45,37 @@ function getProgram(filePath, existingProgram) {
}

try {
// Create a minimal program for single file analysis
const options = {
// Try to locate a tsconfig.json nearest to the file to inherit compiler options (important for path aliases)
const searchPath = path.dirname(filePath);
const configPath = ts.findConfigFile(searchPath, ts.sys.fileExists, 'tsconfig.json');

let compilerOptions = {
target: ts.ScriptTarget.Latest,
module: ts.ModuleKind.CommonJS,
allowJs: true,
checkJs: false,
noEmit: true
noEmit: true,
jsx: ts.JsxEmit.Preserve
};
let rootNames = [filePath];

if (configPath) {
// Read and parse the tsconfig.json
const readResult = ts.readConfigFile(configPath, ts.sys.readFile);
if (!readResult.error && readResult.config) {
const parseResult = ts.parseJsonConfigFileContent(
readResult.config,
ts.sys,
path.dirname(configPath)
);
if (!parseResult.errors || parseResult.errors.length === 0) {
compilerOptions = { ...compilerOptions, ...parseResult.options };
rootNames = parseResult.fileNames.length > 0 ? parseResult.fileNames : rootNames;
}
}
}

const program = ts.createProgram([filePath], options);
const program = ts.createProgram(rootNames, compilerOptions);
return program;
} catch (error) {
throw new ProgramError(filePath, error);
Expand Down
15 changes: 15 additions & 0 deletions tests/analyzeTypeScript.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -971,4 +971,19 @@ test.describe('analyzeTsFile', () => {
const builtInCount = events.filter(e => e.source !== 'custom').length;
assert.ok(builtInCount >= 10, 'Should still include built-in provider events');
});

test('should resolve constants imported via path alias from tsconfig', () => {
const aliasFilePath = path.join(fixturesDir, 'typescript-alias', 'app', 'components', 'main.ts');
// Pass null program to force internal program creation (tsconfig parsing)
const events = analyzeTsFile(aliasFilePath, null, null);

// Expect one event detected with correct name
assert.strictEqual(events.length, 1);
const evt = events[0];
assert.strictEqual(evt.eventName, 'ViewedPage');
assert.strictEqual(evt.source, 'mixpanel');
assert.deepStrictEqual(evt.properties, {
foo: { type: 'string' }
});
});
});
9 changes: 9 additions & 0 deletions tests/fixtures/tracking-schema-all.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1135,3 +1135,12 @@ events:
function: handleView
destination: custom
properties: {}
ViewedPage:
implementations:
- path: typescript-alias/app/components/main.ts
line: 10
function: global
destination: mixpanel
properties:
foo:
type: string
12 changes: 12 additions & 0 deletions tests/fixtures/typescript-alias/app/components/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Test file that imports constants via path alias and triggers tracking
import { TELEMETRY_EVENTS } from 'lib/constants';

// Mock of a tracking library (simple function)
const mixpanel: any = {
track: (..._args: any[]) => {}
};

// Event that should be detected via constant reference
mixpanel.track(TELEMETRY_EVENTS.VIEWED_PAGE, {
foo: 'bar'
});
4 changes: 4 additions & 0 deletions tests/fixtures/typescript-alias/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const TELEMETRY_EVENTS = Object.freeze({
VIEWED_PAGE: 'ViewedPage',
VIEWED_QUESTION: 'ViewedQuestion'
});
14 changes: 14 additions & 0 deletions tests/fixtures/typescript-alias/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "CommonJS",
"strict": true,
"baseUrl": "./",
"paths": {
"lib/*": ["lib/*"]
},
"jsx": "preserve"
},
"include": ["**/*"],
"exclude": ["node_modules"]
}