I'm interested as to whether they did deep code analysis or just added the (not strictly correct, but not terrible) assumption that `this` refers to an instance of the class you're defining. Unfortunately this article is light on links to original source material.
In TypeScript code ‘this’ is typed, and we can infer the type of this for classes and ES5 classes in JS. However we don’t analyze calls to ‘.call’ or ‘.apply’ to infer the type of ‘this’ in a given function.
I think that the best editing experience would be editor combined with debugger. JavaScript is a dynamic language, a lot of autocompletes before run is just guesses. But if you're running program and stopped it at that line, you know all available objects and their properties, so autocomplete would be perfect. It would require a bit different development style, but it might worth it.
Chrome's development tools actually allow saving changes to the JS files AFAIK, or going the other way around, it is possible to debug JS running in the browser from VS Code using the devtools-protocol: https://github.com/ChromeDevTools/devtools-protocol
That's developer problem. I'll provide input values for needed use-case myself (for example with unit test or just manually) and IDE should stop at given line and provide autocomplete. I don't think that there's implementation difficulty, it's just a bit different style of coding, but it should work very well with test driven development.
I don’t think anyone mentioned typescript, which is statically typed and therefore can provide a noImplicitThis check in its linter. AFAIK eslint doesn’t provide that option.
Edit: I misinterpreted the question at first. I guess in TS you declare a class' properties explicitly while in javascript it is usually done in the constructor. The feature must be related to these dynamic properties. Original answer below.
This[0] page says this problem can be also seen in Typescript. But TS has the arrow function as a class method feature that in Javascript is only at stage 2 in it's way to standardization, so it's easier to solve there.
It doesn't complete "this." but rather when you type a member variable it inserts "this.member". ex. type "width" and it replaces it with "this.width".
ES4 tried. Typescript, Flow, and ActionScript's type annotations all inherit almost directly from ES4 (though each diverged to different paths).
I think the next step would be to see someone champion through TC39 the "Python model" where browsers/engines should at least parse and ignore type annotations per spec. Then at least you could pass Typescript/Flow annotated code directly to a browser, even if the browser/engines don't immediately do anything with those extra annotations.
hard to fathom there's an entire generation of developers out there for whom this is entirely new magic. there's an entire class of problem and suite of (bad) tooling that they're having to grapple with to deal with things that simply aren't issues in statically typed languages.
As someone who spent a good 8 years doing development in static languages and then the next 8 or so doing mostly JavaScript, I do occasionally miss the superior tooling available in static languages - and yet, I’m more productive in dynamic languages. This isn’t just experience, I occasionally still write Java and Scala code. But dynamic languages are sufficiently more powerful that the trade off with tooling is worth it.
In Scala the compile time alone is a massive drag.
Are you more productive according to the stopwatch? Or do you just feel more productive?
I believe that a strong static type system with generics is an unmitigated win, and that any time you think you may have lost fighting the compiler is more than regained by the time saved not having to track down type bugs at runtime, using the IDE to perform code completion and refactoring, etc. This effect is magnified as the project grows large.
Dunno, I'm not convinced this is "new magic" for anyone nor that you can learn to program without at least encountering static typing.
It's just that dynamically-typed languages are still popular. And Javascript continually grows and is often a good choice, like for client-side applications. So it's not time wasted if we can figure out ways to improve the tooling experience for these languages.
For example, there are just other considerations beyond static-typing when choosing a language. It seems condescending to suggest that people are unaware rather than just picking different trade-offs.
Not grumpy at all. Of course you are right but solving this problem and making features like this available in dynamically typed languages is a bit tougher and in some cases, not at all possible as certain things might only be known at runtime.
So intellisense news in an IDE is certainly nothing new but it is "newish" for more functional/dynamic languages like JS. JetBrains has also done a good job with their intellisense in their IDEs for Python and Ruby, for example.
The most important word in the title is "class". It only does this properly for JS code inside an explicit ES6 class, and, after some cursory testing, doesn't work too well if you bind things to the class at runtime/outside of the main class block.
VS code doesn't autocomplete "this" for "old-school" (function-provided "this" objects and manual prototype inheritance etc.) object-orientish JS; it doesn't compute it properly or at all for re-bound "this" (e.g. apply()/bind()).
Making autocompletion analysis work for non-straightforward non-ES6 OO javascript is a very hard (maybe impossible even without any "eval"ed code, though I'm not sure) thing to do, so this isn't a ding on VS code: any autocompletion is better than none.
That said, if the only way to get good OO-code autocompletion is to reduce the subset of the language/OO models usable to basically "what you could have in Java/C#", it might be a good opportunity to re-evaluate your choice of language. Transpilers for more easily-statically-analyzable languages do exist, after all.
> ...it might be a good opportunity to re-evaluate your choice of language. Transpilers for more easily-statically-analyzable languages do exist, after all.
I find TypeScript does a pretty good job of allowing as much static analysis as you want without preventing you from using all the features of JS that break it if you wish. In an ideal world I'd work in a fully static language that transpiles to JS but the reality is it's usually necessary to deal with legacy JS libraries. I haven't seen a fully static transpiled language deal with that gracefully, but I'd love to be wrong about that!
With regards to static-analysis capability as an aid to developer productivity, I agree with you: find the compromise that best suits your preference.
With regards to static-analysis capability as a means of ensuring code reliability, I don't agree. Some people (I'm not characterizing you here since I am unsure if this is your position) consider static analyzability to be equivalent to a strong type system or various safety guarantees with regards to making code's behavior verifiable and/or predictable.
If you subscribe to that belief, having an 'escape hatch' that lets you use features of a platform that violate the analyzability guarantees is a virtual guarantee of trouble. I'm put in mind of Rust's "unsafe" areas (there are many other examples of this phenomenon): not only do people routinely overuse them (out of ignorance or misplaced, incorrect, and unverified assertions of "elegance" or "performance"), but their existence at all compromises the ability to verify even small subparts of the greater program: https://news.ycombinator.com/item?id=15146330
Having analyzability/guarantees with an escape hatch is better than having none at all, but a lot of people don't understand the tradeoff there.
That's a good distinction, I agree with you. I hope that some day it's practical write code for browsers that is statically analyzable.
I feel like the programming community was quick to give up static typing when CPUs got fast enough that the performance hit was irrelevant (for many uses), and now we are starting to realize that types were a good idea for correctness too.
> I haven't seen a fully static transpiled language deal with that gracefully, but I'd love to be wrong about that!
Scala.js is specifically designed to be very good at this. You might want to give a shot, if you haven't already ;) Usually you would stay within the type-safe Scala code, or even talk to JavaScript libraries in a type-safe way. But it will always let you do anything you could do in JavaScript (it is one of its core language design decisions). If you want to do something especially weird, the syntax to do so might be pretty awkward, but in most cases it will feel very natural.
In ReasonML, most of your code is just Reason (which is just OCaml), but you can easily provide type declarations and use Javascript code in a typesafe way, or you can just drop to raw JS at any point in a Reason file.
Autocompletion for JS libs has been around for at least 12 years in various tools. It's not that hard of a problem to solve for a standard library if you follow a convention or two.
Edit: I didn't realize this would be a contentious point! I'm not trying to advocate for any particular style of JavaScript, just making the point that places in Java where "this" can be omitted, in JavaScript it cannot.
You must not be very experienced in this language. I write all my large applications without "this". Thankfully, this isn't Java and OOP is entirely avoidable.
Certain frameworks will encourage more of its use (such as with passing functions-as-props in React), but Javascript itself can support multiple paradigms, with the `this` keyword supporting only some of its messiest parts.
Classes are a relatively new feature to the language and certainly aren't required. Classes are a bit troublesome in that lexical scope doesn't directly apply to class parameters, which is why class parameters cannot achieve explicit privacy unlike other references in the language.
The “class” keyword was added fairly recently to simplify Java-style OO programming in JavaScript; it's true that that style of programming requires extensive use of “this” in JS, specifically because, unlike Java, JavaScript is not designed from the ground up to favor that style exclusively.
Java will probably always be a better language for writing Java than JavaScript is, but the Java-in-browsers story has sucked for a long time, so we’re stuck with people who want Java using JavaScript as Java.
I have heard people claim this, but it is evident from their code this is the only way which they are comfortable to program. Insecurity and fear from reliance upon closure and other natural qualities of the language is not a strong justification for verbose bloated constructs lesser developers find emotional comforting.
Yes this feature is completely obvious in hindsight, so much so that I felt a little silly highlighting that we never did it before. I guess sometimes you just need someone to file a feature request to point this type of thing out
Anyways, please try it out in the current VS Code insiders builds and VS Code 1.20 once it is released next week. Lots of much more exciting stuff coming in 1.20 as well
By itself this isn't very impressive, but there's more context in the wings if you pay careful attention to the TypeScript type system. Take a look at https://github.com/Microsoft/TypeScript/pull/21316 and https://github.com/Microsoft/TypeScript/pull/21496. This, combined with the existing literal types and mapped types, allows the TypeScript compiler to understand a huge set of existing practices in the JavaScript world, and do high quality type inference over code that looks totally untypable.
For example, this is a pattern I see a lot:
const args = parseCliFlags({
help: {
parser: Boolean,
description: 'whether to show help',
short: 'h',
},
custom: {
parser: (v: string) => {return {hmmm: 'yes'}},
repeated: true
}
});
if (args.help) {
console.log('help screen!');
}
for (const custom of args.custom) {
console.log(custom.hmmm);
}
So we produce an args object based on the descriptors passed into parseCliFlags. Specifically, we use the parser function and the repeated field. With the above PRs, this code can be fully type checked:
interface Descriptor {
readonly parser: (value: string) => any;
readonly description?: string;
readonly short?: string;
readonly repeated?: true;
}
interface Descriptors {
readonly [flag: string]: Descriptor;
}
type FlagTypes<T> = { [K in keyof T]: FlagTypeOf<T[K]> };
type ReturnTypeOf<V> = V extends (...args: any[])=>infer R ? R : never;
type FlagTypeOf<V> = V extends {repeated: true, parser: (val: string)=> infer R} ? R[] : V extends {parser: (val: string)=> infer R} ? R : never;
That code is tricky to write, but it only needs to be written once, and submitted to DefinitelyTyped. From that point, every VSCode user could benefit from these typings (they already have this part working today, with typings for your node libraries downloaded automatically from DefinitelyTyped).
Perhaps unrelated, but JavaScript has the most annoying handling of "this" in classes that I've ever seen.
I'm constantly typing "bind(this)", while the default case should be that "this" within a class points to the object.
I understand backward compatibility, but I wish there was some command I could add at the beginning of the file to make JS behave normally. Kind of like "use strict".
63 comments
[ 2.9 ms ] story [ 123 ms ] thread1. I mean why hasn't it existed before? Surely an editor can figure out which object you're in and auto-complete this on the basis of that.
2. If it is hard for some reason, how did they do it?
In TypeScript code ‘this’ is typed, and we can infer the type of this for classes and ES5 classes in JS. However we don’t analyze calls to ‘.call’ or ‘.apply’ to infer the type of ‘this’ in a given function.
2. From what I can tell, VS Code DOESN'T solve it. It only autocompletes the "this" object under certain circumstances
(I could be wrong on this last point, since I haven't played around with it first hand)
This[0] page says this problem can be also seen in Typescript. But TS has the arrow function as a class method feature that in Javascript is only at stage 2 in it's way to standardization, so it's easier to solve there.
[0] https://github.com/Microsoft/TypeScript/wiki/'this'-in-TypeS...
`this` is the receiver of the function, which can change depending on how the function is invoked, scope, etc.
It doesn't complete "this." but rather when you type a member variable it inserts "this.member". ex. type "width" and it replaces it with "this.width".
I think the next step would be to see someone champion through TC39 the "Python model" where browsers/engines should at least parse and ignore type annotations per spec. Then at least you could pass Typescript/Flow annotated code directly to a browser, even if the browser/engines don't immediately do anything with those extra annotations.
Just interface {}, as Y, and :type annotations in JavaScript would take us very far.
On that note, how does one even submit an ecmascript proposal?
In Scala the compile time alone is a massive drag.
I believe that a strong static type system with generics is an unmitigated win, and that any time you think you may have lost fighting the compiler is more than regained by the time saved not having to track down type bugs at runtime, using the IDE to perform code completion and refactoring, etc. This effect is magnified as the project grows large.
Or Kotlin for both front/back with transpilation to JS.
It's just that dynamically-typed languages are still popular. And Javascript continually grows and is often a good choice, like for client-side applications. So it's not time wasted if we can figure out ways to improve the tooling experience for these languages.
For example, there are just other considerations beyond static-typing when choosing a language. It seems condescending to suggest that people are unaware rather than just picking different trade-offs.
So intellisense news in an IDE is certainly nothing new but it is "newish" for more functional/dynamic languages like JS. JetBrains has also done a good job with their intellisense in their IDEs for Python and Ruby, for example.
VS code doesn't autocomplete "this" for "old-school" (function-provided "this" objects and manual prototype inheritance etc.) object-orientish JS; it doesn't compute it properly or at all for re-bound "this" (e.g. apply()/bind()).
Making autocompletion analysis work for non-straightforward non-ES6 OO javascript is a very hard (maybe impossible even without any "eval"ed code, though I'm not sure) thing to do, so this isn't a ding on VS code: any autocompletion is better than none.
That said, if the only way to get good OO-code autocompletion is to reduce the subset of the language/OO models usable to basically "what you could have in Java/C#", it might be a good opportunity to re-evaluate your choice of language. Transpilers for more easily-statically-analyzable languages do exist, after all.
I find TypeScript does a pretty good job of allowing as much static analysis as you want without preventing you from using all the features of JS that break it if you wish. In an ideal world I'd work in a fully static language that transpiles to JS but the reality is it's usually necessary to deal with legacy JS libraries. I haven't seen a fully static transpiled language deal with that gracefully, but I'd love to be wrong about that!
With regards to static-analysis capability as a means of ensuring code reliability, I don't agree. Some people (I'm not characterizing you here since I am unsure if this is your position) consider static analyzability to be equivalent to a strong type system or various safety guarantees with regards to making code's behavior verifiable and/or predictable.
If you subscribe to that belief, having an 'escape hatch' that lets you use features of a platform that violate the analyzability guarantees is a virtual guarantee of trouble. I'm put in mind of Rust's "unsafe" areas (there are many other examples of this phenomenon): not only do people routinely overuse them (out of ignorance or misplaced, incorrect, and unverified assertions of "elegance" or "performance"), but their existence at all compromises the ability to verify even small subparts of the greater program: https://news.ycombinator.com/item?id=15146330
Having analyzability/guarantees with an escape hatch is better than having none at all, but a lot of people don't understand the tradeoff there.
I feel like the programming community was quick to give up static typing when CPUs got fast enough that the performance hit was irrelevant (for many uses), and now we are starting to realize that types were a good idea for correctness too.
Scala.js is specifically designed to be very good at this. You might want to give a shot, if you haven't already ;) Usually you would stay within the type-safe Scala code, or even talk to JavaScript libraries in a type-safe way. But it will always let you do anything you could do in JavaScript (it is one of its core language design decisions). If you want to do something especially weird, the syntax to do so might be pretty awkward, but in most cases it will feel very natural.
Edit: I didn't realize this would be a contentious point! I'm not trying to advocate for any particular style of JavaScript, just making the point that places in Java where "this" can be omitted, in JavaScript it cannot.
Certain frameworks will encourage more of its use (such as with passing functions-as-props in React), but Javascript itself can support multiple paradigms, with the `this` keyword supporting only some of its messiest parts.
Java will probably always be a better language for writing Java than JavaScript is, but the Java-in-browsers story has sucked for a long time, so we’re stuck with people who want Java using JavaScript as Java.
Yes this feature is completely obvious in hindsight, so much so that I felt a little silly highlighting that we never did it before. I guess sometimes you just need someone to file a feature request to point this type of thing out
Anyways, please try it out in the current VS Code insiders builds and VS Code 1.20 once it is released next week. Lots of much more exciting stuff coming in 1.20 as well
For example, this is a pattern I see a lot:
So we produce an args object based on the descriptors passed into parseCliFlags. Specifically, we use the parser function and the repeated field. With the above PRs, this code can be fully type checked: That code is tricky to write, but it only needs to be written once, and submitted to DefinitelyTyped. From that point, every VSCode user could benefit from these typings (they already have this part working today, with typings for your node libraries downloaded automatically from DefinitelyTyped).I'm constantly typing "bind(this)", while the default case should be that "this" within a class points to the object.
I understand backward compatibility, but I wish there was some command I could add at the beginning of the file to make JS behave normally. Kind of like "use strict".