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

getDiagnostics throws when the filename does not have an extension #358

Closed
lazarljubenovic opened this issue Jul 10, 2018 · 8 comments
Closed
Labels
compiler api Issue related to an issue in the TypeScript Compiler API

Comments

@lazarljubenovic
Copy link
Contributor

lazarljubenovic commented Jul 10, 2018

Simple:

const project = new Project();
const file = project.createSourceFile("no_extension", "const a: number = 0 as any as number");

console.log(file.getDiagnostics());

For stack trace check the details of the original issue below.

Original issue Taking a stab at the silly #357, I'm getting an error.

Idea

function toString (type: string | Type): string {
  if (typeof type == 'string') {
    return type
  } else {
    return type.getText()
  }
}

export function isAssignable (project: Project, dst: string | Type, src: string | Type): boolean {
  const dstType = toString(dst)
  const srcType = toString(src)

  Error.stackTraceLimit = Infinity

  const content = `const a: ${dstType} = 0 as any as ${srcType}`
  const file = project.createSourceFile(FILENAME, content)
  console.log(content) // => const a: number = 0 as any as number
  const diagnostics = file.getDiagnostics()

  console.log(diagnostics)
}

Test

  describe(`simple cases`, () => {
    const project = new Project()
    const klass = loadClassFromFile('01.ts')('TestClass01', project)

    describe(`using types`, () => {
      fit(`says that "number" is assignable to "number"`, () => {
        const dst = klass.getPropertyOrThrow('p1').getType()
        const src = klass.getPropertyOrThrow('p1').getType()
        expect(isAssignable(project, dst, src)).toBe(true)
      })

Loaded file

class TestClass01 {

  p1: number = 1
  p2: string = '2'
  p3: number | string = 3
  p4: number | null = 4

Stack trace

1) isAssignable simple cases using types says that "number" is assignable to "number"
  Message:
    TypeError: Cannot read property 'flags' of undefined
  Stack:
        at <Jasmine>
        at Object.getCheckFlags (/home/lazar/wane/node_modules/ts-simple-ast/node_modules/typescript/lib/typescript.js:11719:23)
        at getTypeOfSymbol (/home/lazar/wane/node_modules/ts-simple-ast/node_modules/typescript/lib/typescript.js:29638:20)
        at checkVariableLikeDeclaration (/home/lazar/wane/node_modules/ts-simple-ast/node_modules/typescript/lib/typescript.js:45723:41)
        at checkVariableDeclaration (/home/lazar/wane/node_modules/ts-simple-ast/node_modules/typescript/lib/typescript.js:45789:20)
        at checkSourceElement (/home/lazar/wane/node_modules/ts-simple-ast/node_modules/typescript/lib/typescript.js:47748:28)
        at Object.forEach (/home/lazar/wane/node_modules/ts-simple-ast/node_modules/typescript/lib/typescript.js:1762:30)
        at checkVariableStatement (/home/lazar/wane/node_modules/ts-simple-ast/node_modules/typescript/lib/typescript.js:45799:16)
        at checkSourceElement (/home/lazar/wane/node_modules/ts-simple-ast/node_modules/typescript/lib/typescript.js:47717:28)
        at Object.forEach (/home/lazar/wane/node_modules/ts-simple-ast/node_modules/typescript/lib/typescript.js:1762:30)
        at checkSourceFileWorker (/home/lazar/wane/node_modules/ts-simple-ast/node_modules/typescript/lib/typescript.js:47889:20)
        at checkSourceFile (/home/lazar/wane/node_modules/ts-simple-ast/node_modules/typescript/lib/typescript.js:47868:13)
        at getDiagnosticsWorker (/home/lazar/wane/node_modules/ts-simple-ast/node_modules/typescript/lib/typescript.js:47933:17)
        at Object.getDiagnostics (/home/lazar/wane/node_modules/ts-simple-ast/node_modules/typescript/lib/typescript.js:47919:24)
        at /home/lazar/wane/node_modules/ts-simple-ast/node_modules/typescript/lib/typescript.js:76361:85
        at runWithCancellationToken (/home/lazar/wane/node_modules/ts-simple-ast/node_modules/typescript/lib/typescript.js:76324:24)
        at getSemanticDiagnosticsForFileNoCache (/home/lazar/wane/node_modules/ts-simple-ast/node_modules/typescript/lib/typescript.js:76347:20)
        at getAndCacheDiagnostics (/home/lazar/wane/node_modules/ts-simple-ast/node_modules/typescript/lib/typescript.js:76583:26)
        at getSemanticDiagnosticsForFile (/home/lazar/wane/node_modules/ts-simple-ast/node_modules/typescript/lib/typescript.js:76344:20)
        at getDiagnosticsHelper (/home/lazar/wane/node_modules/ts-simple-ast/node_modules/typescript/lib/typescript.js:76286:24)
        at Object.getSemanticDiagnostics (/home/lazar/wane/node_modules/ts-simple-ast/node_modules/typescript/lib/typescript.js:76299:20)
        at Program.getSemanticDiagnostics (/home/lazar/wane/node_modules/ts-simple-ast/dist/compiler/tools/Program.js:76:55)
        at SourceFile.getDiagnostics (/home/lazar/wane/node_modules/ts-simple-ast/dist/compiler/file/SourceFile.js:788:104)
        at Object.isAssignable (/home/lazar/wane/src/compiler/analyzer/utils/type-checker.ts:41:28)
        at UserContext.fit (/home/lazar/wane/src/compiler/analyzer/utils/tests/type-checker.spec.ts:15:16)
        at <Jasmine>
        at runCallback (timers.js:763:18)
        at tryOnImmediate (timers.js:734:5)
        at processImmediate (timers.js:716:5)

Sorry for vague description, I'll create a repro later if you need it.

@dsherret
Copy link
Owner

dsherret commented Jul 10, 2018

Hi @lazarljubenovic,

There's definitely something going on here, but I wasn't able to reproduce when trying this:

const project = new Project();
const file = project.createSourceFile("test.ts", "const a: number = 0 as any as number");

console.log(file.getDiagnostics());

Can you see something I'm missing in that? Maybe it has to do with the way the project is setup.

@dsherret dsherret added the bug label Jul 10, 2018
@dsherret dsherret changed the title getDiagnostics throws getDiagnostics sometimes throws Jul 10, 2018
@lazarljubenovic
Copy link
Contributor Author

lazarljubenovic commented Jul 10, 2018

Oh, wow. My filename did not have the .ts extension. That was it, changing const FILENAME = '__a_stupid_way_to_type_check__' to const FILENAME = '__a_stupid_way_to_type_check__.ts' fixed it.

I guess it should either throw earlier or append .ts by default if no extension?

@dsherret
Copy link
Owner

I had a slight thought that might be the reason.

That would be nice, but right now the TypeScript compiler API doesn't error if supplying a file name without a .ts extension so I'd prefer to stick as close to its behaviour as possible. It would be better for the compiler API to not throw when calling program.getSemanticDiagnostics(...) on a file without an extension. It would be good for me to create some reproduction steps using the compiler API directly.

@lazarljubenovic lazarljubenovic changed the title getDiagnostics sometimes throws getDiagnostics throws when the filename does not have an extension Jul 10, 2018
@dsherret dsherret added compiler api Issue related to an issue in the TypeScript Compiler API and removed bug labels Jul 10, 2018
@lazarljubenovic
Copy link
Contributor Author

Ah... TS internals are weird. Either way, thanks for helping.

@cancerberoSgx
Copy link
Contributor

@dsherret seems is not throwing:

import * as ts from 'typescript'
export function main(code: string, log: (msg: string) => void) {
  const program = createProgram([
    { fileName: 'no_extension', content: 'const a: number = 0 as any as number' },
  ])
  program.getTypeChecker()
  program.emit()
  log(program.getOptionsDiagnostics().length+'')
  log(program.getConfigFileParsingDiagnostics().length+'')
  log(program.getGlobalDiagnostics().length+'')
  log(program.getDeclarationDiagnostics().length+'')
  log(program.getSemanticDiagnostics().length+'')
  log(program.getSyntacticDiagnostics().length+'')
}

/** creates a dummy ts.Program in memory with given source files inside */
export function createProgram(files: {
  fileName: string, content: string,
  sourceFile?: ts.SourceFile
}[], compilerOptions?: ts.CompilerOptions): ts.Program {
  const tsConfigJson = ts.parseConfigFileTextToJson('tsconfig.json',
    compilerOptions ? JSON.stringify(compilerOptions) : `{
    "compilerOptions": {
      "target": "es2018",   
      "module": "commonjs", 
      "lib": ["es2018"],
      "rootDir": ".",
      "strict": true,   
      "esModuleInterop": true,
    }
  `)
  let { options, errors } = ts.convertCompilerOptionsFromJson(tsConfigJson.config.compilerOptions, '.')
  if (errors.length) {
    throw errors
  }
  const compilerHost = ts.createCompilerHost(options)
  compilerHost.getSourceFile = function (fileName: string, languageVersion: ts.ScriptTarget, 
    onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): ts.SourceFile | undefined {
    const file = files.find(f => f.fileName === fileName)
    if (!file) return undefined
    file.sourceFile = file.sourceFile || ts.createSourceFile(fileName, file.content, ts.ScriptTarget.ES2015, true)
    return file.sourceFile
  }
  // in order to typechecker to work we need to implement the following method, the following implementation is enough: 
  compilerHost.resolveTypeReferenceDirectives = function(typeReferenceDirectiveNames: string[], containingFile: string): (ts.ResolvedTypeReferenceDirective | undefined)[] {
    return []
  }
  return ts.createProgram(files.map(f => f.fileName), options, compilerHost)
}

You can copy&paste it in my playground and run it online:

https://typescript-api-playground.glitch.me/

@dsherret
Copy link
Owner

@cancerberoSgx that code won't ever create a file because getSourceFile will always return undefined. This code shows the issue (btw, typescript-api-playground is sweet! Thanks for that!):

import * as ts from 'typescript'
export function main(code: string, log: (msg: string) => void) {
  const fileName = "no_extension";
  const fileText = 'const a: number = 0 as any as number';
  const options = {};
  const compilerHost = ts.createCompilerHost(options);
  const sourceFile = ts.createSourceFile(fileName, fileText, ts.ScriptTarget.Latest, true);
  const program = ts.createProgram([fileName], options, compilerHost);

  log(program.getSemanticDiagnostics(sourceFile).map(d => JSON.stringify(d)).join(", "));
}

I noticed that the program in the compiler API will take file names without an extension and look for files like no_extension.ts, no_extension.d.ts, etc. from the compiler host.

@cancerberoSgx
Copy link
Contributor

aja ! nice thanks! I'm not 100% clear in pure TypeScript compiler API thanks for that tip

@dsherret
Copy link
Owner

I'm not 100% clear in pure TypeScript compiler API

Me neither! 😄 I feel like I'm probably not doing something conventional here, but I'll look into it more later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler api Issue related to an issue in the TypeScript Compiler API
Projects
None yet
Development

No branches or pull requests

3 participants