microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.18k stars 12.38k forks source link

What do we call this command-line option? #2715

Closed RyanCavanaugh closed 9 years ago

RyanCavanaugh commented 9 years ago

Consider two TypeScript files: _a.ts_

module M {
  export var t = 5;
}

_b.ts_

var t = 'foo';
module M {
  var s = t; // <-- what does this mean?
}

If we compile b.ts on its own, the t in var s = t refers to the outer var t, a string. The emit for this line is simply var s = t;.

If we compile b.ts along with a.ts, the t in var s = t refers to M.t, the exported variable of type number. The emit for this line is var s = M.t.

This behavior, and a few other language features (e.g. const enum), prevent TypeScript from being able to correctly operate on a single file without understanding the entire universe of files involved in the program. This has performance implications for features like compile-on-save, especially in larger projects, and blocks scenarios like on-the-fly compilation in module loaders.

We'd really prefer to be able to emit a file without first understanding the entire program it's a part of. The language as-is prevents this. However, by slightly subsetting the language to disallow things that require whole-program binding/typechecking, we can re-enable those scenarios. People can "opt in" their project to this language subset as a trade-off.

Tentatively, we're calling the prongs of this fork "whole program mode" and "single file mode". Naming TBD.

A key part of this is that you need to be able to compile e.g. a.ts and b.ts above in a mode that says "This code will not work in single-file-mode", e.g.:

 > tsc --warnMeIfThisIsNotSafeInSingleFileMode a.ts b.ts
TS1234 (b.ts line 2): Modules may not be re-opened in single-file mode

We need some help naming these things, especially the commandline flag. The current list of ideas on the whiteboard, of which nobody likes anything, consists of the following :frowning:

Please help :bike: :house:!

Ref #2499

JamesKovacs commented 9 years ago

If you're in single-file mode, how do you even know that the module, M, in b.ts is being re-opened?

RyanCavanaugh commented 9 years ago

That's the tricky thing -- this isn't a flag that turns on single-file mode. It's a flag that says "Compile this normally, but error if you do anything that doesn't work in single-file mode"

yahiko00 commented 9 years ago

Just in order to be sure, in such a "single-file-mode", does this following file compile?

b.ts

module M {
  export var t = 5;
}
Steve-Fenton commented 9 years ago

Maybe --segregated, although --singleFileMode seems to say what it does. Maybe put them on the whiteboard until you hate them more than your original ideas :)

RyanCavanaugh commented 9 years ago

Just in order to be sure, in such a "single-file-mode", does this following file compile?

If a.ts is part of the compilation, no. If a.ts is not part of the compilation, then yes.

yahiko00 commented 9 years ago

So, this is not as "single-file" as it would mean... ^_^ Good luck finding an accurate name!

EDIT: I would suggest something like --localBinding

JamesKovacs commented 9 years ago

So what you're really doing is adding linting flags or increasing the warnings level to disallow certain unsafe constructs. Is tsc.exe the right place to put this functionality as it has nothing to do with compiling and a lot to do with whether other tools can make assumptions about potentially unsafe constructs?

matijagrcic commented 9 years ago

-singularCompile, -individualCompile, -bounded Compile (bounded context pattern)

yahiko00 commented 9 years ago

Why not --preventConflict since this is the goal of this option.

RyanCavanaugh commented 9 years ago

Is tsc.exe the right place to put this functionality as it has nothing to do with compiling

The entire point of this flag is to enable the compilation of files without examining their dependencies. 99% of the code to do this checking is in tsc already. A hypothetical external tool to do this checking would start with the tsc.js code and make about five edits. So yes, tsc.exe is absolutely the right place for this.

Why not --preventConflict since this is the goal of this option.

This isn't about conflicts. References outside the declaring file to const enums, for example, are disallowed under this flag because they require inlining, but the compiler doesn't know what value to inline without having that other file available.

Steve-Fenton commented 9 years ago

Maybe --noDependencies or the team suggestion --isolated. Isolated seems to describe the kind of restrictions you may encounter.

yahiko00 commented 9 years ago

@RyanCavanaugh Thanks for your explanations. You know specs better :p

@Steve-Fenton --noDependencies should go on the whiteboard

JamesKovacs commented 9 years ago

I'm confused. You want to compile single files without worrying about "the world", but you want to provide warnings about re-opening modules, which you can only do by looking at the world. What am I missing here?

RyanCavanaugh commented 9 years ago

which you can only do by looking at the world. What am I missing here?

This isn't a flag that says "ignore the world". It's a flag that says "Compile this normally, but error if you do anything that doesn't work if you are not allowed to look at the world"

yahiko00 commented 9 years ago

In this mode, we are not allowed to call a function defined in another file, are we?

RyanCavanaugh commented 9 years ago

This could be seen as a kind of "strict compilation"

The word "strict" is basically reserved for any "use strict" stuff, especially since many ES6 constructs force their enclosing block to be in strict mode.

In this mode, we are not allowed to call a function defined in another file, aren't we?

It's totally allowed. The only things that would be disallowed are:

yahiko00 commented 9 years ago

Since things that are disallowed are quite special cases, you should maybe consider a term with a vague, broad meaning like --safeCompilation. This way, you would be able to add/remove stuffs enclosed in this option in the future

jlanng commented 9 years ago

--standalone

Jaykul commented 9 years ago

I don't like the "safeFor" stuff, I think names like that are leading to the confusion in the thread -- you're not changing the compilation, you're just adding warnings to the output. How about a flag named a bit more to the point, like: warnForSingleFileUse (or just warnForSingleFile) or even warnOnCrossFileDependencies (or just warnMultiFileSyntax)

vladima commented 9 years ago

What about making it not a boolean flag but rather a two valued enum, something like: enum CompilationUnit {program, individialFile} so in command line it would be:

 -compileAs program // default one
- compileAs individualFile
yahiko00 commented 9 years ago

@Jaykul If we refer to the cases given by @RyanCavanaugh above, I think any term including "singleFile" or "dependency" a bit too strong since all dependencies are not disallowed but only few of them. Also the compilation would look at the project scope for resolving "simple" dependencies like functions, Thus, the compilation will not be restricted to a "single file", a misleading term I think.

Jaykul commented 9 years ago

@yahiko00 -- have I misunderstood? The idea is a flag which would be passed when compiling all the files which will warn you about stuff that would prevent you from getting the exact same result if you compiled individual files.

That is: warn me if there's anything that will break if I compile individual files one at a time.

Thus, warnForSingleFileUse -- since the only reason you'd care about these warnings is if you want to be able to to compile single files.

stephanedr commented 9 years ago

What about something like warnEmitDependencies (warn if the emitted JS of a TS file depends on other TS files).

Steve-Fenton commented 9 years ago

Maybe the flag could be the answer to the question "why would I use this flag?", for example if it is to allow faster single-file compilation, it could be quickLimitedCompile, for example.

basarat commented 9 years ago

Sidetrack: such issues don't exist when only using external modules right ?

basarat commented 9 years ago

Perhaps it's implicit on if module is specified Implicit off if people use --out.

mhegazy commented 9 years ago

@basarat there are still the const enum from an ambient declaration that needs to be flagged here.

basarat commented 9 years ago

@mhegazy can you give me an example of what changes in emit of a file depending on a constant enum in another file.

My follow up question will be "does anything bad happen if we don't do this different emit"

Steve-Fenton commented 9 years ago

The use of the const enum is replaced by the value (i.e. the number is inlined).

mhegazy commented 9 years ago

@basarat emit is not conditioned on the flag. the differences is you will get an error if you use the flag with a full compilation. the example is as follows:

// a.d.ts 
declare const enum E {
   a = 0
}
// b.ts
var x = E.a;

in full program compilation mode:

// b.js 

var x = 0 /* E.a */;

in single file emit:

// b.js 

var x = E.a;

The problem is at single file emit, we have no way to know that E is a const enum, we only emit it as a property access. this might work if a.d.ts was built using ---pereserveConstEnum but will fail with reference error otherwise. The flag will warn you about these cases to ensure your program is safe for single file compilation.

you can see the implementation in PR #2550.

basarat commented 9 years ago

@mhegazy With this new flag, What would be the emit for:

// b.ts
var x = E.a;

(assume E is declared as a const enum but of course with this flag you cannot look at that fact).

mhegazy commented 9 years ago

The emit would be the same.

The flag is not "emit one file at a time" it is "tell me what breaks if I build one file at a time"

basarat commented 9 years ago

@mhegazy If I have one file a.ts (the one that I am compiling):

module M {
  var s = t;
}

And another file we can't look at:

module M {
  export var t = 5;
}

And yet another we can't look at:

var t = 5;

Would single compiling a.ts still be considered an error?

mhegazy commented 9 years ago

The current behavior is no.

But if you call ts.transpile it will give u an error because a.ts is not an external module.

Would u propose to make that an error in the command line case?

basarat commented 9 years ago

Would u propose to make that an error in the command line case

No.

With that answer, why can't we get people to just use external modules everywhere? Are we making our lives difficult getting --out to work efficiently? Its not going to be safe for cases like :

module M {
  var s = t;
}

Which is essentially anything that's ambient right?


rant. Because there is no good answer I would suggest that instead of this feature ... we do a different feature:

But I guess this is exactly what ts.transpile means?

I don't like out

/rant

mhegazy commented 9 years ago

But I guess this is exactly what ts.transpile means?

yes :D, that is where we landed. that still leaves you with the const enum from an ambient declaration issue..

basarat commented 9 years ago

that still leaves you with the const enum from an ambient declaration issue..

Got it. We want to expose ts.transpile on some command line flag. Implicit with this flag is:


Reason for no --out : you don't want the build server output to be different from any local fast compile.

basarat commented 9 years ago

Again my feature suggestion:

noExternalLookups

locally:

tsc --preserveConstantEnums --module commonjs --noExternalLookups a.ts b.ts

build server

tsc --preserveConstantEnums --module commonjs a.ts b.ts


@mhegazy can you provide a local vs. build server flags split with the proposed (call it isolated) flag? Or am I still understanding it wrong?

mhegazy commented 9 years ago

@basarat i was not thinking of the server/local scenario, but that should work as well.. here is the scenario i had in mind.

you are using something like JSPM, or es6-module-loader (with TypeScript support), system.js enabled website with TypeScript as a transpiler ..etc.. in these work flows, there are no build step. transpilation is happening on the fly, no errors reported from the compiler, only runtime errors from your browser. i.e. the compiler is called through ts.transpile..

now, you would have some tooling, e.g. VS, atom, sublime texts, etc.. you would set the flag ---noExternalLookups in your tsconfig.json, and you will get some red squiggles under const enums use sites, for instance. if you fix all these errors in your editor, you are guaranteed you will get no unexpected runtime-errors when you hit F5 in your browser, or deploy your jspm package.

the same can be said in a workflow like atom plugins, in your editor/local build you would use the flag, then deploy to atom and that will grantee that it is safe to transpile using typescript-simple.

basarat commented 9 years ago

here is the scenario i had in mind

@mhegazy That scenario can be served by:

This way any errors in the tooling are errors at runtime otherwise runtime will work fine.

Does that not cater for it, or do we still needs this flag?

mhegazy commented 9 years ago

even with --perseveContantEnum, if you are consuming a const enum from another .d.ts that was not built with --perserveConstantEnum, it is going to fail at runtime.

basarat commented 9 years ago

Got it. Effectively this new flag is for IDE's to:

With the purpose of safe for per file transpilation.

So : checkSafeForTranspile.

mhegazy commented 9 years ago

for future references, the flag name is --isolatedModules

altano commented 8 years ago

I noticed the autocomplete in VSCode for my tsconfig included the isolatedModules option. The current documentation is "Unconditionally emit imports for unresolved files." This didn't mean anything to me so I searched around and found this conversation, which sounds completely different than the definition in the docs. Can I get some clarification on what this option does?

mhegazy commented 8 years ago

I makes the compiler behave as if you called ts.transpile (https://github.com/Microsoft/TypeScript/issues/2499) on each file sparely.

There are some rules in place, i.e. const enums are always preserved, no namespaces, and the emitter will emit some checks when inlining types for decorators.

if you are not using ts.transpile to transpile your file one at a time, you do not need to care about this flag.

dazinator commented 7 years ago

@mhegazy +1 for updating the current docs for this argument, with a better explanation or a link to a more detailed explanation like the one you have just given! :-)