Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3f16848
fix(plugin-typescript): include extended options
BioPhoton Aug 20, 2025
51c755b
test(plugin-typescript): fix path for windows
BioPhoton Aug 20, 2025
16eab1f
test(plugin-typescript-e2e): update snapshot
BioPhoton Aug 20, 2025
a440788
test(plugin-typescript-e2e): update test config
BioPhoton Aug 20, 2025
38c09e6
test(plugin-typescript): fix path for windows
BioPhoton Aug 20, 2025
bd6e003
test(plugin-typescript-e2e): fix path for windows
BioPhoton Aug 20, 2025
686ab91
fix(plugin-typescript-e2e): fix lint
BioPhoton Aug 20, 2025
062d9be
test(plugin-typescript-e2e): fix path for windows robust variant
BioPhoton Aug 20, 2025
f8220d9
test(plugin-typescript-e2e): fix path for windows robust variant 2
BioPhoton Aug 20, 2025
18f397c
test(plugin-typescript-e2e): fix path for windows robust variant 3
BioPhoton Aug 20, 2025
8210562
test(plugin-typescript-e2e): remove comments
BioPhoton Aug 20, 2025
8a63794
Update e2e/plugin-typescript-e2e/tests/collect.e2e.test.ts
BioPhoton Aug 20, 2025
25d636d
Update packages/plugin-typescript/src/lib/runner/utils.ts
BioPhoton Aug 20, 2025
c03338f
test: adjust test helper for message transform
BioPhoton Aug 20, 2025
7e1e45c
test(plugin-typescript): adjust checks
BioPhoton Aug 20, 2025
87851bb
test(plugin-typescript-e2e): adjust checks
BioPhoton Aug 20, 2025
6ec8df3
test(plugin-typescript-e2e): adjust checks 2
BioPhoton Aug 20, 2025
a0b5814
chore: add cloud-id
BioPhoton Aug 20, 2025
574beaa
chore(plugin-typescript-e2e): adjust checks for windows
BioPhoton Aug 20, 2025
2e1f646
chore(plugin-typescript-e2e): adjust checks for windows 3
BioPhoton Aug 20, 2025
5b22c58
chore: remove nx cloud id
BioPhoton Aug 20, 2025
e96b718
fix: adjust targets
BioPhoton Aug 20, 2025
af8634d
Update packages/plugin-typescript/src/lib/runner/runner.int.test.ts
BioPhoton Aug 21, 2025
68350d8
fix: format
Aug 21, 2025
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
2 changes: 1 addition & 1 deletion code-pushup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const config: CoreConfig = {
server: env.CP_SERVER,
apiKey: env.CP_API_KEY,
organization: env.CP_ORGANIZATION,
project: env.CP_PROJECT,
project: 'cli-workspace',
},
}),

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"compilerOptions": {
"rootDir": "./src",
"rootDir": "${configDir}/src",
"target": "ES6",
"module": "CommonJS",
"strict": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ exports[`PLUGIN collect report with typescript-plugin NPM package > should run p
"details": {
"issues": [
{
"message": "TS6059: File './exclude/utils.ts' is not under 'rootDir' 'src'. 'rootDir' is expected to contain all source files.",
"message": "TS6059: File './exclude/utils.ts' is not under 'rootDir' './src'. 'rootDir' is expected to contain all source files.",
"severity": "error",
"source": {
"file": "tmp/e2e/plugin-typescript-e2e/src/6-configuration-errors.ts",
Expand Down
30 changes: 29 additions & 1 deletion e2e/plugin-typescript-e2e/tests/collect.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,35 @@ import {
E2E_ENVIRONMENTS_DIR,
TEST_OUTPUT_DIR,
omitVariableReportData,
osAgnosticAuditOutputs,
osAgnosticPath,
teardownTestFolder,
} from '@code-pushup/test-utils';
import { executeProcess, readJsonFile } from '@code-pushup/utils';

function sanitizeReportPaths(report: Report): Report {
return {
...report,
plugins: report.plugins.map(plugin => ({
...plugin,
audits: osAgnosticAuditOutputs(plugin.audits, message =>
message.replace(
/['"]([^'"]*[/\\][^'"]*)['"]/g,
(fullMatch: string, capturedPath: string) => {
const osAgnostic = osAgnosticPath(capturedPath);
// Only replace directory paths, not .ts file paths
if (capturedPath.endsWith('.ts')) {
return `'${osAgnostic}'`;
}
// on Windows the path starts from "plugin-typescript-e2e/src" not "./". This normalizes it to "./<segment>"
return `'${['.', osAgnostic.split('/').slice(-1)].join('/')}'`;
},
),
),
})),
};
}

describe('PLUGIN collect report with typescript-plugin NPM package', () => {
const envRoot = path.join(E2E_ENVIRONMENTS_DIR, nxTargetProject());
const distRoot = path.join(envRoot, TEST_OUTPUT_DIR);
Expand Down Expand Up @@ -57,6 +82,9 @@ describe('PLUGIN collect report with typescript-plugin NPM package', () => {
path.join(envRoot, outputDir, 'report.json'),
);
expect(() => reportSchema.parse(reportJson)).not.toThrow();
expect(omitVariableReportData(reportJson)).toMatchSnapshot();

expect(
omitVariableReportData(sanitizeReportPaths(reportJson)),
).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"compilerOptions": {
"rootDir": "${configDir}",
"verbatimModuleSyntax": false
},
"include": ["src/**/*.ts"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.extends-base.json",
"compilerOptions": {
"verbatimModuleSyntax": true,
"module": "CommonJS"
},
"exclude": ["src/*-errors/**/*.ts"]
}
18 changes: 11 additions & 7 deletions packages/plugin-typescript/src/lib/runner/runner.int.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { describe, expect } from 'vitest';
import type { AuditOutputs } from '@code-pushup/models';
import { osAgnosticAuditOutputs } from '@code-pushup/test-utils';
import { getAudits } from '../utils.js';
import { createRunnerFunction } from './runner.js';

describe('createRunnerFunction', () => {
it('should create valid audit outputs when called', async () => {
await expect(
createRunnerFunction({
tsconfig:
'packages/plugin-typescript/mocks/fixtures/basic-setup/tsconfig.all-audits.json',
expectedAudits: getAudits(),
})(() => void 0),
).resolves.toMatchSnapshot();
const runnerFunction = createRunnerFunction({
tsconfig:
'packages/plugin-typescript/mocks/fixtures/basic-setup/tsconfig.all-audits.json',
expectedAudits: getAudits(),
});

const result = await runnerFunction();

expect(osAgnosticAuditOutputs(result as AuditOutputs)).toMatchSnapshot();
}, 35_000);
});
2 changes: 1 addition & 1 deletion packages/plugin-typescript/src/lib/runner/ts-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export type DiagnosticsOptions = {
export async function getTypeScriptDiagnostics({
tsconfig,
}: DiagnosticsOptions): Promise<readonly Diagnostic[]> {
const { fileNames, options } = await loadTargetConfig(tsconfig);
const { fileNames, options } = loadTargetConfig(tsconfig);
try {
const program = createProgram(fileNames, options);
return getPreEmitDiagnostics(program);
Expand Down
62 changes: 38 additions & 24 deletions packages/plugin-typescript/src/lib/runner/utils.int.test.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import * as tsModule from 'typescript';
import { describe, expect } from 'vitest';
import { describe, expect, vi } from 'vitest';
import { osAgnosticPath } from '@code-pushup/test-utils';
import { loadTargetConfig } from './utils.js';

describe('loadTargetConfig', () => {
const parseConfigFileTextToJsonSpy = vi.spyOn(
tsModule,
'parseConfigFileTextToJson',
);
const readConfigFileSpy = vi.spyOn(tsModule, 'readConfigFile');
const parseJsonConfigFileContentSpy = vi.spyOn(
tsModule,
'parseJsonConfigFileContent',
);

it('should return the parsed content of a tsconfig file and ist TypeScript helper to parse it', async () => {
await expect(
it('should return the parsed content of a tsconfig file and ist TypeScript helper to parse it', () => {
expect(
loadTargetConfig(
'packages/plugin-typescript/mocks/fixtures/basic-setup/tsconfig.init.json',
osAgnosticPath(
'packages/plugin-typescript/mocks/fixtures/basic-setup/tsconfig.init.json',
),
),
).resolves.toStrictEqual(
).toStrictEqual(
expect.objectContaining({
fileNames: expect.any(Array),
options: {
module: 1,
configFilePath: undefined,
configFilePath: expect.stringContaining('tsconfig.init.json'),
esModuleInterop: true,
forceConsistentCasingInFileNames: true,
skipLibCheck: true,
Expand All @@ -31,25 +31,39 @@ describe('loadTargetConfig', () => {
},
}),
);
expect(parseConfigFileTextToJsonSpy).toHaveBeenCalledTimes(1);
expect(parseConfigFileTextToJsonSpy).toHaveBeenCalledWith(
'packages/plugin-typescript/mocks/fixtures/basic-setup/tsconfig.init.json',
expect.stringContaining('/* Projects */'),
expect(readConfigFileSpy).toHaveBeenCalledTimes(1);
expect(readConfigFileSpy).toHaveBeenCalledWith(
expect.stringContaining('tsconfig.init.json'),
expect.any(Function),
);
expect(parseJsonConfigFileContentSpy).toHaveBeenCalledTimes(1);
expect(parseJsonConfigFileContentSpy).toHaveBeenCalledWith(
});

it('should return the parsed content of a tsconfig file that extends another config', () => {
expect(
loadTargetConfig(
'packages/plugin-typescript/mocks/fixtures/basic-setup/tsconfig.extends-extending.json',
),
).toStrictEqual(
expect.objectContaining({
compilerOptions: expect.objectContaining({
esModuleInterop: true,
forceConsistentCasingInFileNames: true,
module: 'commonjs',
skipLibCheck: true,
strict: true,
target: 'es2016',
fileNames: expect.arrayContaining([
// from tsconfig.extends-base.json#includes and tsconfig.extends-extending.json#excludes
expect.stringMatching(/src[/\\]0-no-diagnostics[/\\]/),
]),
options: expect.objectContaining({
// Options from tsconfig.extends-base.json
rootDir: expect.stringMatching(/basic-setup$/),
// Options from tsconfig.extends-extending.json
module: 1,
configFilePath: expect.stringContaining(
'tsconfig.extends-extending.json',
),
verbatimModuleSyntax: true, // Overrides base config's false
}),
}),
expect.any(Object),
expect.any(String),
);

expect(readConfigFileSpy).toHaveBeenCalledTimes(1);
expect(parseJsonConfigFileContentSpy).toHaveBeenCalledTimes(1);
});
});
27 changes: 16 additions & 11 deletions packages/plugin-typescript/src/lib/runner/utils.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
// eslint-disable-next-line unicorn/import-style
import { dirname } from 'node:path';
import path from 'node:path';
import {
type Diagnostic,
DiagnosticCategory,
flattenDiagnosticMessageText,
parseConfigFileTextToJson,
parseJsonConfigFileContent,
readConfigFile,
sys,
} from 'typescript';
import type { Issue } from '@code-pushup/models';
import { readTextFile, truncateIssueMessage } from '@code-pushup/utils';
import { truncateIssueMessage } from '@code-pushup/utils';
import { TS_CODE_RANGE_NAMES } from './ts-error-codes.js';
import type { CodeRangeName } from './types.js';

Expand Down Expand Up @@ -84,7 +83,7 @@ export function getIssueFromDiagnostic(diag: Diagnostic) {
return {
...issue,
source: {
file: diag.file.fileName,
file: path.relative(process.cwd(), diag.file.fileName),
...(startLine
? {
position: {
Expand All @@ -96,16 +95,22 @@ export function getIssueFromDiagnostic(diag: Diagnostic) {
} satisfies Issue;
}

export async function loadTargetConfig(tsConfigPath: string) {
const { config } = parseConfigFileTextToJson(
tsConfigPath,
await readTextFile(tsConfigPath),
);
export function loadTargetConfig(tsConfigPath: string) {
const resolvedConfigPath = path.resolve(tsConfigPath);
const { config, error } = readConfigFile(resolvedConfigPath, sys.readFile);

if (error) {
throw new Error(
`Error reading TypeScript config file at ${tsConfigPath}:\n${error.messageText}`,
);
}

const parsedConfig = parseJsonConfigFileContent(
config,
sys,
dirname(tsConfigPath),
path.dirname(resolvedConfigPath),
{},
resolvedConfigPath,
);

if (parsedConfig.fileNames.length === 0) {
Expand Down
7 changes: 6 additions & 1 deletion packages/plugin-typescript/vitest.int.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ export default defineConfig({
coverage: {
reporter: ['text', 'lcov'],
reportsDirectory: '../../coverage/plugin-typescript/int-tests',
exclude: ['mocks/**', '**/types.ts'],
exclude: [
'mocks/**',
'**/types.ts',
'**/index.ts',
'vitest.{unit,int}.config.ts',
],
},
environment: 'node',
include: ['src/**/*.int.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
Expand Down
7 changes: 6 additions & 1 deletion packages/plugin-typescript/vitest.unit.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ export default defineConfig({
coverage: {
reporter: ['text', 'lcov'],
reportsDirectory: '../../coverage/plugin-typescript/unit-tests',
exclude: ['mocks/**', '**/types.ts'],
exclude: [
'mocks/**',
'**/types.ts',
'**/index.ts',
'vitest.{unit,int}.config.ts',
],
},
environment: 'node',
include: ['src/**/*.unit.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
Expand Down
2 changes: 1 addition & 1 deletion project.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@code-pushup/cli-source",
"name": "cli-workspace",
"$schema": "node_modules/nx/schemas/project-schema.json",
"targets": {
"code-pushup": {
Expand Down
17 changes: 10 additions & 7 deletions testing/test-utils/src/lib/utils/os-agnostic-paths.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import type {
AuditOutput,
AuditOutputs,
AuditReport,
} from '@code-pushup/models';
import type { AuditOutput, AuditReport } from '@code-pushup/models';

const AGNOSTIC_PATH_SEP_REGEX = /[/\\]/g;
const OS_AGNOSTIC_PATH_SEP = '/';
Expand Down Expand Up @@ -102,6 +98,7 @@ export function osAgnosticPath(filePath?: string): string | undefined {

export function osAgnosticAudit<T extends AuditOutput | AuditReport>(
audit: T,
transformMessage: (message: string) => string = s => s,
): T {
const { issues = [] } = audit.details ?? {};
if (issues.every(({ source }) => source == null)) {
Expand All @@ -119,12 +116,18 @@ export function osAgnosticAudit<T extends AuditOutput | AuditReport>(
...issue.source,
file: osAgnosticPath(issue.source.file),
},
message: transformMessage(issue.message),
},
),
},
};
}

export function osAgnosticAuditOutputs(audits: AuditOutputs): AuditOutputs {
return audits.map(osAgnosticAudit);
export function osAgnosticAuditOutputs<T extends AuditOutput | AuditReport>(
audits: T[],
transformAuditIssueMessage?: (message: string) => string,
): T[] {
return audits.map(audit =>
osAgnosticAudit(audit, transformAuditIssueMessage),
);
}
Loading