Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: fix missing semantic diagnostics in program #150

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
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 'ts-patch'

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);
});
});