Repository navigation
Suggestion: Add the nameof compile-time operator to convert property and function names into strings #1579
Description
Activity
Yeah, closing this as a dupe, suffice to say we definitely get the pain people feel here.
Reacted by LegendsReacted by n074v41l4bl34u, Kaelin Laundry, Eric, Uwy, Tomáš Hübelbauer, Robert Santana and Meikel- addedDuplicateAn existing issue was already createdAn existing issue was already created
on Jan 6, 2015 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.
Though I don't have a clue how to add it to language without conflicting with other features...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...
Reacted by Legends, FW-Luki, Robert Santana and Tomáš HübelbauerAgree. Doesnt look like a duplicate. A nameof operator should be considered as a very good idea. Probably "easy" to implement too.
I use typescript with angularjs and I like to create static string constants inside my controller/directive classes that I use when I register these classes in an angular module. Instead of hardcoding these names I would like to use nameof(MyDirectiveClass) instead.
Its the same kind of problem that we experienced earlier with PropertyChanged events in WPF (before nameof/CallerMemberName) when you renamed stuff.Reacted by branko-d, David Pfeffer, Ralf Seidel, mak-in, Tomáš Hübelbauer, Robert Santana and Jonatan Nyqvist- addedSuggestionAn idea for TypeScriptAn idea for TypeScriptIn DiscussionNot yet reached consensusNot yet reached consensusand removedDuplicateAn existing issue was already createdAn existing issue was already created
on Jul 28, 2015 RyanCavanaugh commented
on Jul 28, 2015 MemberMore actionsGood 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:
class Foo { prop: string; } function isFoo(obj: Foo): boolean; function isFoo(obj: any) { return (typeof obj === 'object') && (obj !== null) && (nameof(Foo.prop) in obj); } // Output for isFoo() function isFoo(obj: any) { return (typeof obj === 'object') && (obj !== null) && ('prop' in obj); }Is that a correct assumption?
Frode Gilberg (@frodegil) , that would be an awesome use case. Would reduce a lot of code-smell and repetitive copy/pasting from my daily work-flow.
Boris Letocha (@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.
Bryan Rayner (@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.
166 remaining items
Load more actionsdecorators is not good example to follow
Reacted by Roberto MalatestaRyanCavanaugh commented
on Apr 9, 2019 MemberMore actionsOn the other hand, nameof isn't a reserved word in JS, so the chances of TC39 adding it as an operator is quite low.
Unary prefix operators don't need to be reserved words because
op expris already an invalid expression for anyopthat isn't an operator. For example,awaitwas not previously a reserved word, but there wasn't any problem adding it to the language because it only ever appears in an unambiguous unary operator position.Reacted by Titian Cernicova-Dragomir, Pauan, Dave, Daniel Turan and Stevendragomirtitian commented
on Apr 9, 2019 ContributorMore actionsMight I also point out ew do have
keyofwhich is actually much more powerful thannameof, the result ofnameofis just astring,keyoflets us constrain input much better.While not as pretty as the
nameofoperator, we can usekeyofto do something similar enough using a function IMO: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.
Titian Cernicova-Dragomir (@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.
Reacted by Tomáš Hübelbauer, Erin and Laicasaaneandretshurotshka (@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.
Reacted by Tomáš HübelbauerTitian Cernicova-Dragomir (@dragomirtitian) it's been talked about in the collapsed posts within this issue.
To summarize,
keyofhelps in the scenarios you mentioned, but still doesn't get some common use cases where class or interface/type identifier names would be nice to keep synced up.Ex. when using dependency injection frameworks (
@inject(nameof(Something))), throwing nice argument error messages...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 (
logger.log(nameof(ClassName), LogLevel.Info, "Some log message.")), and probably more.There definitely is some benefit to adding
nameof, but in my opinion it shouldn't be done by TypeScript as this suggestion doesn't meet this guideline:- This could be implemented without emitting different JS based on the types of the expressions.
Edit: My bad, was thinking of this non-goal:
- Add or rely on run-time type information in programs, or emit different code based on the results of the type system. Instead, encourage programming patterns that do not require run-time metadata.
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
nameof(Identifier)is only a little bit better than writing"Identifier"in JS due to no compile time errors when using an incorrectly named identifier (I guess there could be runtime ones, but that's not as nice). Also, TC39 wouldn't include support for nameof for identifiers in the type domain (such as interface and type alias names).Its place is probably for this to be a compiler plugin (shameless plug) that people can choose to include in their projects.
Reacted by Titian Cernicova-DragomirWith regarding whether this type of feature is the a good fit for TypeScirpt based on the guideline mentioned by David Sherret (@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.
There definitely is some benefit to adding
nameof, but in my opinion it shouldn't be done by TypeScript as this suggestion doesn't meet this guideline:- This could be implemented without emitting different JS based on the types of the expressions.
Could you clarify as to what different JS means in this context? For example, if
nameof<InterfaceA>andnameof<InterfaceB>both output a string that have different values but share the same type, is that considered different?James L. Walsh (@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:
- Add or rely on run-time type information in programs, or emit different code based on the results of the type system. Instead, encourage programming patterns that do not require run-time metadata.
👍 Please add this
James (@rjamesnw)
In case of numbers in expression, I did some modification:function nameof(selector: () => any, fullname = false) { var s = '' + selector; var m = s.match(/return\s+([A-Z0-9$_.]+)/i) || s.match(/.*?(?:=>|function.*?{)\s*([A-Z0-9$_.]+)/i); var name = m && m[1] || ""; return fullname ? name : name.split('.').reverse()[0]; }- locked and limited conversation to collaborators
on Jun 25, 2019 RyanCavanaugh commented
on Jun 25, 2019 MemberMore actionsLocking Waiting for TC39 threads as policy since there's not really anything to talk about except complaining that TC39 hasn't done it yet 🙃
RyanCavanaugh commented
on Jan 25, 2024 MemberMore actionsClosing since there's no action available on our side. Will reopen if/when something happens at committee.
I would like to see the
nameofoperator 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,
nameofconverts 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: