Skip to content

Commit

Permalink
fix: fix missing semantic diagnostics in program
Browse files Browse the repository at this point in the history
  • Loading branch information
artem1458 committed Apr 2, 2024
1 parent b4b50de commit 627a7b7
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 4 deletions.
25 changes: 21 additions & 4 deletions projects/patch/src/ts/create-program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ namespace tsp {
rootNames = createOpts.rootNames;
options = createOpts.options;
host = createOpts.host;
oldProgram = createOpts.oldProgram;
oldProgram = <tsShim.Program>(createOpts.oldProgram);
configFileParsingDiagnostics = createOpts.configFileParsingDiagnostics;
} else {
options = options!;
Expand All @@ -98,9 +98,12 @@ namespace tsp {
}

/* Invoke TS createProgram */
let program: tsShim.Program & { originalEmit?: tsShim.Program['emit'] } = createOpts ?
tsShim.originalCreateProgram(createOpts) :
tsShim.originalCreateProgram(rootNames, options, host, oldProgram, configFileParsingDiagnostics);
let program: tsShim.Program & {
originalEmit?: tsShim.Program['emit'];
originalGetSemanticDiagnostics?: tsShim.Program['getSemanticDiagnostics'];
} = createOpts ?
<tsShim.Program>(tsShim.originalCreateProgram(createOpts)):
<tsShim.Program>(tsShim.originalCreateProgram(rootNames, options, host, oldProgram, configFileParsingDiagnostics));

/* Prevent recursion in Program transformers */
const programTransformers = pluginCreator.createProgramTransformers();
Expand Down Expand Up @@ -151,6 +154,20 @@ namespace tsp {
return result;
}

/* Hook getSemanticDiagnostics method for ts-loader */
if (!program.originalGetSemanticDiagnostics) {
program.originalGetSemanticDiagnostics = program.getSemanticDiagnostics;
program.getSemanticDiagnostics = newGetSemanticDiagnostics;
}

function newGetSemanticDiagnostics(sourceFile: tsShim.SourceFile, cancellationToken?: tsShim.CancellationToken) {
const originalDiagnostics = program.originalGetSemanticDiagnostics!(sourceFile, cancellationToken);
const addedDiagnostics = tsp.diagnosticMap.get(program) || [];
const diagnosticsByFile = addedDiagnostics.filter(it => it.file === sourceFile);

return originalDiagnostics.concat(diagnosticsByFile);
}

return program;
}
}
7 changes: 7 additions & 0 deletions test/assets/projects/transformer-extras/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "transformer-extras-test",
"main": "src/index.ts",
"dependencies": {
"ts-node" : "^10.9.1"
}
}
34 changes: 34 additions & 0 deletions test/assets/projects/transformer-extras/src/compiler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const path = require('path');

(() => {
const tsInstance = require('ts-patch/compiler');

const configPath = path.join(process.cwd(), `tsconfig.json`);
const configText = tsInstance.sys.readFile(configPath);
const configParseResult = tsInstance.parseConfigFileTextToJson(configPath, configText);
const config = configParseResult.config;

config.compilerOptions.noEmit = false;
config.compilerOptions.skipLibCheck = true;
config.compilerOptions.outDir = 'dist';

const sourceFilePath = path.join(__dirname, 'index.ts');
const program = tsInstance.createProgram({
rootNames: [ sourceFilePath ],
options: config.compilerOptions,
});

const emitResult = program.emit();
const sourceFile = program.getSourceFile(sourceFilePath);
const semanticDiagnostics = program.getSemanticDiagnostics(sourceFile);

process.stdout.write(`emitResultDiagnostics:${diagnosticsToJsonString(emitResult.diagnostics)}\n`);
process.stdout.write(`semanticDiagnostics:${diagnosticsToJsonString(semanticDiagnostics)}\n`);
})();

function diagnosticsToJsonString(diagnostics): string {
return JSON.stringify(diagnostics.map(diagnostic => {
const { file, start, length, messageText, category, code } = diagnostic;
return { file: file.fileName, start, length, messageText, category, code };
}));
}
1 change: 1 addition & 0 deletions test/assets/projects/transformer-extras/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const a: string = 42;
44 changes: 44 additions & 0 deletions test/assets/projects/transformer-extras/src/transformer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// @ts-nocheck
import type * as ts from 'typescript'
import type { TransformerExtras } from '../../../../../dist'

export default function(program: ts.Program, pluginOptions: unknown, transformerExtras?: TransformerExtras) {
return (ctx: ts.TransformationContext) => {
return (sourceFile: ts.SourceFile) => {
transformerExtras?.addDiagnostic({
file: sourceFile,
code: 42,
messageText: 'It\'s a warning message!',
category: 0,
start: 0,
length: 1,
});
transformerExtras?.addDiagnostic({
file: sourceFile,
code: 42,
messageText: 'It\'s an error message!',
category: 1,
start: 1,
length: 2,
});
transformerExtras?.addDiagnostic({
file: sourceFile,
code: 42,
messageText: 'It\'s a suggestion message!',
category: 2,
start: 2,
length: 3,
});
transformerExtras?.addDiagnostic({
file: sourceFile,
code: 42,
messageText: 'It\'s a message!',
category: 3,
start: 3,
length: 4,
});

return sourceFile;
};
};
}
15 changes: 15 additions & 0 deletions test/assets/projects/transformer-extras/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"include": [
"src"
],
"compilerOptions": {
"outDir": "dist",
"module": "commonjs",
"target": "esnext",
"plugins" : [
{
"transform": "./src/transformer.ts"
}
]
}
}
89 changes: 89 additions & 0 deletions test/tests/transformer-extras.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { prepareTestProject } from '../src/project';
import { execSync } from 'child_process';
import path from 'path';

/* ****************************************************************************************************************** *
* Tests
* ****************************************************************************************************************** */

describe('Transformer Extras addDiagnostics', () => {
let projectPath: string;
let output: string[];

beforeAll(() => {
const prepRes = prepareTestProject({ projectName: 'transformer-extras' });
projectPath = prepRes.tmpProjectPath;

let commandOutput: string;
try {
commandOutput = execSync('ts-node src/compiler.ts', {
cwd: projectPath,
env: {
...process.env,
PATH: `${projectPath}/node_modules/.bin${path.delimiter}${process.env.PATH}`
}
}).toString();
}
catch (e) {
const err = new Error(e.stdout.toString() + '\n' + e.stderr.toString());
console.error(err);
throw e;
}

output = commandOutput.trim().split('\n');
});

test('Provide emit result diagnostics and semantic diagnostics and merge it with original diagnostics', () => {
const [ emitResultDiagnosticsText, semanticDiagnosticsText ] = output;

const emitResultDiagnostics = JSON.parse(emitResultDiagnosticsText.split('emitResultDiagnostics:')[1]);
const semanticDiagnostics = JSON.parse(semanticDiagnosticsText.split('semanticDiagnostics:')[1]);

const filePath = path.join(projectPath, 'src/index.ts');
const expectedEmitResultDiagnostics = [
{
file: expect.stringContaining(filePath),
code: 42,
start: 0,
length: 1,
messageText: 'It\'s a warning message!',
category: 0
}, {
file: expect.stringContaining(filePath),
code: 42,
start: 1,
length: 2,
messageText: 'It\'s an error message!',
category: 1
}, {
file: expect.stringContaining(filePath),
code: 42,
start: 2,
length: 3,
messageText: 'It\'s a suggestion message!',
category: 2
}, {
file: expect.stringContaining(filePath),
code: 42,
start: 3,
length: 4,
messageText: 'It\'s a message!',
category: 3
}
];
const expectedSemanticDiagnostics = [
{
file: expect.stringContaining(filePath),
code: 2322,
category: 1,
length: 1,
messageText: 'Type \'number\' is not assignable to type \'string\'.',
start: 13,
},
...expectedEmitResultDiagnostics,
]

expect(emitResultDiagnostics).toEqual(expectedEmitResultDiagnostics);
expect(semanticDiagnostics).toEqual(expectedSemanticDiagnostics);
});
});

0 comments on commit 627a7b7

Please sign in to comment.