Repository navigation
createProgram corrupts updated SourceFile #26166
Copy link
Copy link
Closed
Labels
QuestionAn issue which isn't directly actionable in codeAn issue which isn't directly actionable in code
Description
TypeScript Version: 3.1.0-dev.20180801
Search Terms:
Code
For the last two days I'm investigating a crash of my linter when fixing files, updating existing SourceFiles and creating a Program from these SourceFiles: fimbullinter/wotan#361
First you need 2 files that are processed by the repro script:
tsconfig.json
{
"include": [
"test.ts"
]
}test.ts
export class Rule {
private getPropertyInfo() {
return
}
}
(name) => b;Now add the following script (using __dirname in the script assumes it's in the same directory as the above files, that can be changed though):
repro.js
"use strict";
const ts = require("typescript");
const {strictEqual} = require("assert");
const config = ts.parseJsonConfigFileContent(ts.readJsonConfigFile(__dirname + '/tsconfig.json', ts.sys.readFile), ts.sys, __dirname, undefined, __dirname + '/tsconfig.json');
let program = ts.createProgram(config.fileNames, config.options, ts.createCompilerHost(config.options, true));
let sourceFile = program.getSourceFile(__dirname + '/test.ts');
// add a semicolon after `return`
const newCode = sourceFile.text.slice(0, 66) + ';' + sourceFile.text.slice(66);
sourceFile = ts.updateSourceFile(sourceFile, newCode, { newLength: 1, span: { start: 66, length: 0 } });
strictEqual(sourceFile.statements[1].getSourceFile(), sourceFile, 'parent reference should be updated')
const originalHost = ts.createCompilerHost(config.options, true);
const host = {
...originalHost,
getSourceFile(path, version) {
if (path === sourceFile.fileName)
return sourceFile;
return originalHost.getSourceFile(path, version);
},
};
program = ts.createProgram(program.getRootFileNames(), program.getCompilerOptions(), host, program);
strictEqual(program.getSourceFile(__dirname + '/test.ts'), sourceFile, 'new Program should contain the updated SourceFile');
strictEqual(sourceFile.statements[1].getSourceFile(), sourceFile, 'parent reference should still be the same');Execute node repro.js
Expected behavior:
All assertions pass
Actual behavior:
Last assertion fails.
There is no error if I use ts.createSourceFile instead of ts.updateSourceFile
Metadata
Metadata
Assignees
Labels
QuestionAn issue which isn't directly actionable in codeAn issue which isn't directly actionable in code
Activity
RyanCavanaugh commentedon Aug 2, 2018
The comments on
updateSourceFileare instructive:The default implementation of
getSourceFilereturns a new object every time and this is generally what you should do on your end unless you have guarantees that the object you recycle hasn't been modified.ajafff commentedon Aug 2, 2018
Note that the script no longer uses the old SourceFile object after it's updated.
So this must be a bug in
createProgramas it assumes all old sourcefiles are still valid and its Nodes can be mutated at will.RyanCavanaugh commentedon Aug 2, 2018
createProgramwill always attempt to re-use the source files from the lastoldProgramparameter - if a program contains source files that have been mutated into invalidness byupdateSourceFile, it's not a safe argument to provide tooldProgramajafff commentedon Aug 3, 2018
I disagree. It always uses the new SourceFiles returned from the host. It only tries to avoid processing files that haven't changed.
I looked through all of the code involved in creating a Program from an oldProgram. Everything is working as expected and nothing could break even with old (now invalid) SourceFiles that were updated. There's just one line that assumes every SourceFile is still valid:
https://github.com/Microsoft/TypeScript/blob/9df88316a2f26a83dd5635e149038f0c10168e63/src/compiler/program.ts#L963
If I add
program.getTypeChecker()after creating the initial Program, it works because parent pointers are only updated during creation of the TypeChecker.ajafff commentedon Aug 3, 2018
I tried rewriting that function to no longer depend on TypeChecker or Nodes within the old SourceFiles. I don't fully understand why it was implemented that way. So maybe my implementation doesn't do exactly the same.
createProgram: don't use TypeChecker