-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Suggestion: Add the nameof compile-time operator to convert property and function names into strings #1579
Comments
Yeah, closing this as a dupe, suffice to say we definitely get the pain people feel here. |
I don't see this as dupe of any mentioned issues. This could be critical to support advanced minification together with metaprogramming. Currently there is no way how to get member string after minification. In C# this works other way - you get unminified string. In Typescript I would need minified string. So maybe this could be actually different proposal :-) As unminified would be good too. Just some thoughs... |
Agree. Doesnt look like a duplicate. A nameof operator should be considered as a very good idea. Probably "easy" to implement too. |
Good point -- the other issues don't quite cover what this is talking about |
👍 I'm using Typescript in a project at work, and am putting in a run-time duck type checker. The use case is de-serializing URL parameters. If this feature was in the language, this is how I would expect the syntax to work:
Is that a correct assumption? |
@frodegil , that would be an awesome use case. Would reduce a lot of code-smell and repetitive copy/pasting from my daily work-flow. @Bobris, one would hope that the minified strings for the purposes of my example, I think if minification were to make its way into the tsc, this would be desired behavior. |
@bryanerayner , your assumption is correct. The nameof operator is only used during compilation to convert type information into a string. module pd {
export class MyAngularControllerClass {
public static IID : string = nameof(MyAngularControllerClass); // "MyAngularControllerClass"
public static $inject: string[] = [Model1.IID, Model2.IID, "$scope"];
constructor(model1: Model1, model2:Model2, $scope:angular.IScope) {
}
public get nameOfThisGetter() : string {
return nameof(this.nameOfThisGetter); // "nameOfThisGetter";
}
}
angular.module(nameof(pd)).controller(MyAngularControllerClass.IID, MyAngularControllerClass);
// angular.module("myapp").controller("OldName", MyAngularControllerClass); < out of sync
} Its so easy to get these hardcoded strings out-of-sync during refactoring |
This would be fantastic. |
Fantastic indeed. So much could be done with such an operator. |
Well, the case partially described by @frodegil about managing components that are registered somehow with a name (that most of the times is the same name of the I agree and understand how adding a new sintax element is undesired and even goes against typescript's main goal (to be a thin superset of javascript), but I still consider this proposal a very nice one. Another small caseI use pouchdb and I make my own doc IDs, for performance and consistency reasons. These docIds are generated using a few fields, and one of them is based on the name of the model being saved, like: For now I need to maintain a property called className on all my models: class Person {
className = 'Person';
_id: string;
$_dateCreated: number;
}
var p:Person;
p._id = `${this.className}_${window.appMetadata.cordovaUuid}__${this.$_dateCreated}__${window.uuid.v4()}`
pouchdb.put(p) which could be turned into: class Person {
_id: string;
$_dateCreated: number;
}
var p:Person;
p._id = `${nameof Person}_${window.appMetadata.cordovaUuid}__${this.$_dateCreated}__${window.uuid.v4()}`
pouchdb.put(p) Foreseeable issuesI can see it becoming a bit harder to figure out the Thin alternativesConsidering that all cases that deal with property names can be handled completely by either #394 or #1003, I'd say the exposure of a |
A use case where neither #394 , #1003 ,getClass() nor GetClassName() would help would be this in Angular2: @Component({
selector: 'my-selector',
template: `<h1>{{${nameof MyComponent.prototype.title}}}</h1>`
})
class MyComponent {
public title = "Hellow world";
} It would makes the code refactoring proof, but it also makes it a lot less readable, so I'm not sure if is such a good idea. Just a thought. |
Adding suggestion here to keep Enums in mind for nameof(MyModule.Enums.FooEnum.Bar) === "Bar" would compile to: MyModule.Enums.FooEnum[MyModule.Enums.FooEnum.Bar] === "Bar" or just:
|
A good use case is for unit tests where SinonJS is used for spying/stubbing. With the proposed var obj = new MyClass();
sinon.stub(obj, 'methodName', () => null); // Prone to break. var obj = new MyClass();
sinon.stub(obj, nameof(MyClass.methodName), () => null); Or, if the stub would support to pass in a predicate to resolve the name: var obj = new MyClass();
function stub<T>(targetObject: T, targetMethodPredicate: (T)=>string, methodStub: Function){}
sinon.stub(obj, x => nameof(x.methodName), () => null); |
I have a use case for constructing SQL strings. Here is a code snippet: interface User {
id: string;
name: string;
birthday: Date;
}
// Current implementation
var sql = `SELECT id,name FROM Users`;
// Desired implementation
var sql = `SELECT ${nameof(User.id)},${nameof(User.name)} FROM ${nameof(User)}s`; The I agree this should be a compile time transformation like it is in C# so we can burn in the type name. |
@RyanCavanaugh Maybe time to revisit? Strings are bad because you can't "Find All References" them, you can't "Refactor/Rename" and they are prone to typos. Some examples, using Aurelia: let p: Person;
// Observe a property for changes
let bindingEngine: BindingEngine;
bindingEngine.observeProperty(p, 'firstName')
.subscribe((newValue: string) => ...);
class Person {
firstName: string;
// Declare a function to call when value changes
@bindable({ changeHandler: 'nameChanged'})
lastName: string;
nameChanged(newValue: string, oldValue: string) { ... }
// Declare computed property dependencies
@computedFrom('firstName', 'lastName')
get fullName() {
return `${firstName} ${lastName}`;
}
}
// Declare dependencies for validation
validation.on(p)
.ensure('firstName').isNotEmpty()
.ensure('fullName', config => config.computedFrom(['firstName', 'lastName'])).isNotEmpty(); There you have five different APIs that would all benefit from having a |
Exactly what I want |
@rayncc no need to use |
I disagree, |
С# has |
@stasberkov |
The expression nameof(XXX) will be compiled to a constant string, which I think is totally compatible with JS |
@rayncc What he meant is: what if JS later adds in a Then there would be a conflict, which has to be resolved in some way (and there aren't any easy answers for that). On the other hand, |
I don't think that this case is different from generics or typings that can be implemented in JavaScript in a differrent way in the future... TypeScript then should support it while also supporting the TS way of implementing those concepts. |
The are so many use cases for this, for example NativeScript's notifyPropertyChange( nameof( property) ) method. BTW, they have done this before with the decorator stuff. The solution there was to simply hide it behind an 'experimental' compiler flag. |
@markusmauch lol, look how this ended up for them |
decorators is not good example to follow |
Unary prefix operators don't need to be reserved words because |
Might I also point out ew do have While not as pretty as the function nameof<T>(k: keyof T) : keyof T {
return k
}
class Person {
static defaultName: string;
name!: string;
studies!: {
highSchool: string
unversity: string
}
}
let foo : { bar: { baz : { x: number }}}
let k1 = nameof<Person>("name")
let k2 = nameof<Person['studies']>("unversity")
let k3 = nameof<typeof Person>("defaultName")
let k4 = nameof<typeof foo['bar']['baz']>("x") For the simple case it looks decent, for nested paths, it does look a bit wonky, and you can't use it on privates. |
@dragomirtitian even though that acomplishes the {access members by name in a type safe approach} issue (which is awesome), that won't make the compiler automatically help developers on refactorings, nor would it help the IDE look for references of a member without doing text search. |
@goodmind I really don't see your point. I agree that decorators turned out to be quite different than originally anticipated but that's what 'experimental' means. It means 'use it but be aware that it might change'. Anyway, I'm glad I can use them now and when they become part of the JavaScript language I will either change my code accordingly or keep the compiler switch in place. The same could be done with nameof. Decorator support was added to win Googe over. But obviously the needs of the community are not equally important. |
@dragomirtitian it's been talked about in the collapsed posts within this issue. To summarize, Ex. when using dependency injection frameworks ( function add(a: number, b: number) {
throwArgumentErrorIfNaN(a, nameof(a));
throwArgumentErrorIfNaN(b, nameof(b));
return a + b;
} ..., in test description names... import { CustomCollection } from "some-library";
describe(nameof(CustomCollection), () => {
describe(nameof<CustomCollection>(c => c.add), () => {
});
}); ..., when writing log statements ( There definitely is some benefit to adding
Edit: My bad, was thinking of this non-goal:
Also, I don't see what motivation TC39 would have for adding this to JS as JS developers wouldn't benefit from this because writing Its place is probably for this to be a compiler plugin (shameless plug) that people can choose to include in their projects. |
With regarding whether this type of feature is the a good fit for TypeScirpt based on the guideline mentioned by @dsherret I wonder if there is a more library centric way that could be used by linters to pick up stringly typed code that encodes the intention more clearly to access the name of a variable or property. I tried playing around quickly with a concept using ES6 tagged template literals but unfortunately it does appear that one can access the original template string including the template parameter syntax which would have been a nice way to both provide runtime checking as well as allowing simple one liner cases for variables. The basic idea works reasonably for simple one level deep properties though and the syntax is arguably a bit better than a simple function call with parameters passed in. const props = {
name : "Little John",
slogan: "Rolls are rolls and tools are tools."
}
console.log(nameof`${props}.slogan`) // slogan
console.log(nameof`${props}.foo`) // ERROR: Property 'foo' not found on object Ultimately what I think we really want is a somewhat more common way to express the name symbol name resolution that could be caught easier by some compiler/transpiler, linter, or at least runtime in that preferred order to reduce bugs and to provide for more resilient code needs to have these names available in some form at run-time. |
Could you clarify as to what different JS means in this context? For example, if |
@JLWalsh I think I might have misread that as not emitting different JS based on the kinds of expressions (I was half going from memory when I copied and pasted it). So taking a call expression and emitting a string literal. I'm not sure now exactly what that point means now, but my brain is fried after programming for way too long today. I was thinking of this non-goal:
|
👍 Please add this |
@rjamesnw
|
Locking Waiting for TC39 threads as policy since there's not really anything to talk about except complaining that TC39 hasn't done it yet 🙃 |
Closing since there's no action available on our side. Will reopen if/when something happens at committee. |
I would like to see the
nameof
operator be considered for Typescript.This feature was just added to C# description, and it is an elegant solution to a common issue in Javascript.
At compile time,
nameof
converts its parameter (if valid), into a string. It makes it much easier to reason about "magic strings" that need to match variable or property names across refactors, prevent spelling mistakes, and other type-safe features.To quote from the linked C# article:
(if x == null) throw new ArgumentNullException(nameof(x));
To show another example, imagine you have this Person class:
If I have an API that requires me to specify a property name by string (pretty common in JS), I am forced to do something like this:
But if I misspell
firstName
, I'll get a runtime error.So this is the type-safe equivalent:
The text was updated successfully, but these errors were encountered: