Closed Taytay closed 8 months ago
I've not checked the transforms API but I'd imagine it would be relatively easy to implement nameof
in a transform. The transform may even be shipped with the compiler, but not an official part of it. From this point of view, it's been a really smart move from the team not to implement it prematurely.
@gcnew What easy? nameof([put valid identifier here])
-> last part of that identifier as string. I mean it's a glorified macro. In fact, at work we are just about to add a function nameof(thing:any)
that gets replaced with exactly what @frodegil suggested above at post-compile with regex or whatever. I'm sure there are some edge cases that technically might require some more thought, but this is basically covering all use cases expressed in this thread. It would just make an infinity of sense to add nameof()
it to a language plagued with magic strings and lack of reflection - instead we get keyof()
. Palm. Face.
@gleno keyof
is a very useful feature. The fact that the problems it solves are different from what people want from nameof
is another story.
From my point of view nameof
should not be implemented because it scavenges a valid identifier and adds non-standard expression level syntax. When Transforms API gets merged-in, it should be fairly straightforward to write a transfrom yourself or use the best community one.
@gcnew my understanding that transforms are from existing syntax -> existing syntax, nameof
is new syntax so it's missing out, i hope i am wrong
@aleksey-bykov Yes, I think so as well. However if you have a function declaration such as:
declare function nameof(x: any): string;
and a transform that replaces all global nameof
invocations with its argument stringified, it should work.
Edit: such a solution will not be sufficient where a string literal is expected. Nontheless it's a nice first step and the more compiler APIs become available the more it could be improved on.
@gcnew: If this is gonna be an official solution for nameof scenarios, then please write it to documentation, for example here: https://www.typescriptlang.org/docs/handbook/gulp.html
@gcnew If you look at Roslyn, the extension points are diagnostics, fixes and refactorings which provide help during development and have no risk of using any of them. The last thing I want to do is adding random packages which modify the very heart of the build pipeline, the compilation and supporting these non-existing language constructs with different hacks. And then end up with problems like "we can't upgrade to TypeScript 2.x, because "nameof package" 0.1.2 does not support it yet, so 1) we will have to wait or 2) remove all usages of nameof from our code and loose it; or 3) reimplement it ourselves and maintain it forever" in a few months.
@Peter-Juhasz who makes you download it from npm? make it yourself and be your own 24/7 customer service
transformation api has very little to do with these circumstances
@Peter-Juhasz I feel for you and I do agree that nameof
would be useful and add safety. However TypeScript is about modelling ECMAScript, not making up a new better language. Features such as keyof
are purely type level, that's why they are getting implemented. And even for them there is resistance to allocate a keyword. In contrast, nameof
is strictly an expression level construct. I don't think the team will ever be convinced to implement it, unless it's in the ECMAScript spec. I myself don't like the idea of scavenging the nameof
identifier.
This leaves us with two options:
The first option will take years (if it ever gets approved), the second should be decent enough. I have no experience with using the TS APIs, but I'd expect them to be stable and backward-compatible, thus the nameof package should be stable as well.
I disagree Marin, nameof isn't a language feature, it only enables type-checking (similarly to keyof), once compiled nameof disappears and its argument is left as a string. I don't see why this would need to be part of ecmascript if keyof doesn't.
On Feb 14, 2017 19:19, "Marin Marinov" notifications@github.com wrote:
@Peter-Juhasz https://github.com/Peter-Juhasz I feel for you and I do agree that nameof would be useful and add safety. However TypeScript is about modelling ECMAScript, not making up a new better language. Features such as keyof are purely type level, that's why they are getting implemented. And even for them there is resistance from the team to allocate a keyword. In contrast, nameof is strictly an expression level construct. I don't think the team will ever be convinced to implement it, unless it's in the ECMAScript spec. I myself don't like the idea of scavenging the nameof identifier.
This leaves us with two options:
- make an ECMAScript proposal and hope it gets traction
- use the public Transfroms API and have a good approximation
The first option will take years (if it ever gets approved), the second should be decent enough. I have no experience with using the TS APIs, but I'd expect them to be stable and backward-compatible, thus the nameof package should be stable as well.
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/Microsoft/TypeScript/issues/1579#issuecomment-279790055, or mute the thread https://github.com/notifications/unsubscribe-auth/ABOZ9c-85P3x5AMakVDuxlNkH56me3POks5rcfAYgaJpZM4DNVgi .
At least for interfaces, that go poof after compiling (no code produced and no impact on JS Standard) we could have helper functions that expose all their metadata. It's rather trivial (I'm currently doing it with a bash script), and it would be really useful to have not only nameOf, but implements/extends informaton, the list of fields, fields names and their arity (signature)as well.
Honk me if interested, we could write a proposal and in the worst case a pre-processor. (time and motivation permitting)
--R
@gcnew:
1. nameof will never be in ES spec. I'm pretty confident to say so, because nameof doesn't make much sense in EcmaScript. Here's why: Because of dynamic typing in javascript, nameof(myObject.MyProperty)
is exactly the same as "myProperty"
. Both are basically "magic strings". There is no reason to use the verbose nameof syntax in javascript. Or do you see any advandage of using nameof in javascript?
As @RyanCavanaugh mentioned, only standardized ES features or features that will never be in ES spec can be considered for typescript in order to avoid confusion like it happend with modules. This is the second case.
I'm a pragmatic programmer and I don't care how it can be done if it can be done. Choose whatever you think's best for the product considering ES compatibility, easy-of-use, performance or whatever you think is relevant. If you decide to go with Transfroms API, let it be so, but for god's sake, this issue deserves a solution.
@Liero You can never know whether a valid identifier would be used in future versions. It might not be used by ECMA directly or not for the purpose discussed in-here, but it might find its use in browser APIs. Consider a global function nameof(node: Node): string | undefined
which reads the name
attribute of a DOM node. Will such a function ever be standardised? Probably not this one but maybe one concerning certificates or Symbol
s. The point is nameof
cannot be borrowed as a keyword. There is code already using it for a local variable names and future APIs may use it as well. It would be an unfortunate breaking change, whilst diverging from the ECMAScript specification.
The solution is either to lift nameof
to the type level, or implement nameof
as an optional pre/post processing step, that is not part of the official language.
A type-level, fully erasable nameof
might be:
declare const options: {
easing?: string,
duration: number
};
if (!options.easing) {
throw new Error(<nameof options.easing> 'easing');
}
nameof(path.to.identifier)
will never survive the compilation process.Seeing nameof
in the final compiled code is definitely and exactly NOT what everyone asking for the feature wants. We want it to disappear, and leave behind the string "identifier"
, which makes the implementation of nameof
as a compile/type level precisely what it needs to be. The only difference between:
type AliasToUnion = A | B;
and
nameof(object.property)
is the outcome: one gives an empty string and the other gives a non-empty string. Both are type level structures, and both have their shell disappear once the compilation is done.
If now the argument is that we can't risk a keyword collision between typescript's nameof
and future ecma's nameof
, then I'd say the same argument applies to pretty much everything typescript has that is not in the intersection with es8 drafts, which is basically a lot of type stuff, thus making this argument quite bogus.
Even if we don't go the keyword way, this pretty much sums for me why I still think it's optimally typescript's job to do this (be it a transformation, a compile level function, a type level structure, an ecma proposal, etc):
The very basic purpose of TypeScript is to provide static typing and allow for better refactoring tools. Property names are required very often in DataBinding scenarios and doing so in type safe manner is exactly what you would expect from TypeScript.
<nameof options.easing> 'easing'
Seems like it almost does it, but we'd have to mannually refactor that not-so-magical string anyway, with the only difference that we'd maybe have the compiler tell us that it's value does not represent the actual name of the identifier. It's a refactor->compile->see_error->go_back_and_properly_refactor
flow instead of a refactor_properly
one. With that, I'd rather insist in a more complete version instead of this proposal. Now, @gcnew, if we could make <>
a structure that didn't need a value to be cast, that'd be awesome (I don't think we can atm).
guys, nameof
is sure needed, in mean while there is 90% capable workaround which only takes a bit of extra typing:
type NamesOf<T> = { [P in keyof T]: P }
interface Data {
name: string;
value: number;
}
const propertyNames: NamesOf<Data> = {
name: 'name,' // <-- not a magic string anymore, CAN ONLY BE `name`
value: 'value' // <-- not a magic string anymore, CAN ONLY BE `value`
}
@gcnew:
Consider a global function nameof(node: Node): string | undefined
usage in existing javascript files - no problem at all. In typescript conflicts can be avoided by:
var _nameof = window.nameof; //problem solved
BTW, in javascript the function would be probably called nameOf
@fredgalvao: your suggestion: <nameof options.easing>
already conflicts with JSX, resp TSX
@Liero that was not my suggestion, I was just commenting on @gcnew 's suggestion.
Ok, what if there was typeside nameof operator?
Something that would look like this:
const obj = {x:1};
function abc(thing:nameof(obj.x)){ }
Which would behave exactly as
function abc(thing:"x"){ }
It's not as clean as nameof(accessor)->string
, but I think it could be quite useful.
I consider nameOf
as a compile-time keyword only, and can't see any runtime or global use of it (never say never, but its unlikely, IMO - call it TSNameOf
, and it's even more unlikely)
Should we be afraid of collisions? If, or when, EcmaScript becomes a language that need the nameOf
keyword in the future, I assume it has evolved so far, and adopted all features of TS so that we won't need TS any longer.
nameOf
in TS should behave the exact same way as nameOf
in C#
C# (v5) specs:
The nameof expression is a constant. In all cases, nameof(...) is evaluated at compile-time to produce a string. Its argument is not evaluated at runtime, and is considered unreachable code (however it does not emit an "unreachable code" warning).
I've spent too much time "refactoring" string-literals in my TS/Angular code by now. After all, the main purpose of Typescript is to be a strongly-typed alternative, right?
Hurry, include the compile-time nameOf
keyword before TS becomes obsolete :)
What about a custom nameOf
function like the following?
declare const options: {
easing: string,
duration: number,
};
function nameOf<T, K extends keyof T>(_: T, key: K) {
return key;
}
nameOf(options, 'easing'); // 'easing'
nameOf({ options }, 'options'); // options
PS: keyof
refactoring is tracked by https://github.com/Microsoft/TypeScript/issues/11997.
I have a very simple implementation of nameof
implemented with the transformation api (#13940) that I did quickly this morning (See this branch of ts-nameof—specifically this file and used in this file).
If we had a way in tsconfig.json to specify paths to external before & after transformations while compiling I feel that this would be more powerful than having implementations directly in the compiler.
Edit: The library now supports babel and the typescript compiler.
For my two cents, I'd love to see nameof purely to get rid of strings in bindings with Inversify.
I was just looking into this as well. I was hoping this would be an obvious feature implemented by now. I was hoping to get event string names from method names in a custom event setup (bla bla bla ;)), among other things (such as storing namespace and type metadata, etc.). Hopefully, we won't need to discuss this for another 2 years.
I would love to see that, too. I'm working with some ASP.NET AJAX legacy code and have to deal with stuff like:
DerivedClass.registerClass( "DerivedClass", BaseClass );
It would be really nice to do this instead:
DerivedClass.registerClass( nameof( DerivedClass ), BaseClass );
Currently, renaming DerivedClass
will break the Code.
nameof() would be nice for using Immutable.js Record.
This code
class MyRecord extends Record({value: ""}) {
readonly value: string;
withValue(newValue: string) {
return this.set("value", newValue) as this;
}
}
can be rewritten like
class MyRecord extends Record({value: ""}) {
readonly value: string;
withValue(newValue: string) {
return this.set(nameof(this.value), newValue) as this;
}
}
, which is safe for renaming.
Potential workaround for name of class or variable:
function nameof<T, P extends keyof T>(descriptor: {[P in keyof T]?: T[P]; }): P
{
for(var key in descriptor) {
if(descriptor.hasOwnProperty(key)) {
return key as P;
}
}
}
class Test { }
var test= ""
var x = nameof({ Test }) // x: "Test" = "Test"
var y = nameof({ test }) // y: "test" = "test"
@jankaspar Is it possible to have Test.foo with your example where Test - class, foo - class property?
@vytautas-pranskunas- No, for that you are much better using the mapped types. Something like this:
class Test {
foo: string
}
function propertyName<T>(name: keyof T){
return name;
}
propertyName<Test>("foo")
propertyName<Test>("bar") // error
note that it's not perfect. I have attempted using things like that and it failed, because the interfaces were too complex, and the compiler, instead of generating "field1" | "field2"
for the keyof T
, generated something like any
, and anything would compile.
Thank you! Spark by Readdle
Is there any consensus on how to do this with classes and interfaces? I see how I can just keyof
but basically I want to use nameof(type) so it would make logging easier e.g., LogManager.getLogger(nameof(MyClass))
@niemyjski This is available with standard JavaScript.
class MyClass {
hello(n) { return 'hello ' + n; }
}
console.log(MyClass.name);
See Function.name
@styfle That fails if the code is minified, which is why this is asking for a compile-time operator that produces a string in the output and won't be affected by minification
@dpogue most minifiers give an option to disable name mangling (globally, or for annotated functions/classes); that might satisfy?
It may also be surprising to some that nameof(MyClass) != MyClass.name
(in the minified case)
Also, #8 and #16037 might have some related discussions to keep an eye on
Yes, we need something reliable...
Thanks -Blake Niemyjski
On Wed, Jul 19, 2017 at 11:47 AM, Ian MacLeod notifications@github.com wrote:
@dpogue https://github.com/dpogue most minifiers give an option to disable name mangling (globally, or for annotated functions/classes); that might satisfy?
Also, #8 https://github.com/Microsoft/TypeScript/issues/8 and #16037 https://github.com/Microsoft/TypeScript/issues/16037 might have some related discussions to keep an eye on
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/Microsoft/TypeScript/issues/1579#issuecomment-316447179, or mute the thread https://github.com/notifications/unsubscribe-auth/AA-So1aBKzLHIsS4-wYSeZmtNOf55B9Rks5sPjMYgaJpZM4DNVgi .
@nevir and @dpogue this solution may not work where you're attempting to resolve by interface and not a concrete type via DI - i.e. does not solve for interface name availability at runtime.
I have found a need for something like this recently sincekeyof
does not allow qualified identifiers.
Since the API I used in my example now supports qualified names using dotted path strings, the augmentation I was using to improve its type safety:
export function computedFrom<K1 extends string, K2 extends string>
(prop1: K1, prop2: K2): (target: {[P in K1 | K2]}, key: string | number | symbol) => void;
export function computedFrom<K extends string>
(prop: K): (target: {[P in K]}, key: string | number | symbol) => void;
can no longer express its capabilities.
Why after all this time, TS still has no mechanism to (e.g. nameof
) to eliminate MAGIC STRINGS?
I don't understand why this thread has so much begging, justifications, and use cases. It's a no brainer - this should be a top priority. It's the most horrible part of JS which TS has not yet solved.
+1
this would be a useful feature
So what's the story people, is this happening?
As @jakkaj mentioned earlier, this would be a really great feature to use with inversify.
Here's an example of what you currently have to do when you use interfaces:
interface Weapon {
hit(): string;
}
// Define an object that contains all of the type
// symbols so that we only use magic strings once.
let TYPES = {
Weapon: Symbol("Weapon") // Magic String! Oh No!
};
@injectable()
class Ninja {
constructor(@inject(TYPES.Weapon) weapon: Weapon) { }
}
With a nameof
operator, you could at least get rid of the magic string, but you could also get rid of the object that holds all the type symbols:
interface Weapon {
hit(): string;
}
@injectable()
class Ninja {
constructor(@inject(Symbol(nameof(Weapon))) weapon: Weapon) { }
}
I believe that strings can also be used to declare what is injected, so you could even change the constructor to this:
@injectable()
class Ninja {
constructor(@inject(nameof(Weapon)) weapon: Weapon) { }
}
@reduckted
Here's an example of what you currently have to do when you use interfaces:
interface Weapon { hit(): string; } // Define an object that contains all of the type // symbols so that we only use magic strings once. let TYPES = { Weapon: Symbol("Weapon") // Magic String! Oh No! }; @injectable() class Ninja { constructor(@inject(TYPES.Weapon) weapon: Weapon) { } }
Actually, that is not a magic string. You could just as well write
const types = { // this should be frozen in real code.
Weapon: Symbol()
};
and the implications would be the same (all consuming code must reference the Weapon
property of the types
object).
With a nameof operator, you could at least get rid of the magic string, but you could also get rid of the object that holds all the type symbols:
interface Weapon { hit(): string; } @injectable() class Ninja { constructor(@inject(Symbol(nameof(Weapon))) weapon: Weapon) { } }
This would likely not have the desired effect as each time Symbol
is called it creates a new unique value
Symbol("x") === Symbol("x") // false
I believe that strings can also be used to declare what is injected, so you could even change the constructor to this:
@injectable() class Ninja { constructor(@inject(nameof(Weapon)) weapon: Weapon) { } }
If the proposed functionality is analogous to C#'s nameof
operator, this would result in @inject("Weapon")
, introducing hidden global dependencies and token collisions.
Something like
interface Weapon {
hit(): string;
}
const Weapon = Symbol();
export default Weapon;
seems preferable but it is still not ideal since. The trouble is keeping the names in sync to sustain declaration merging, but is not related to the actual values.
There are plenty of other reasons to want nameof
, but I don't think it is a good fit for DI.
@aluanhaddad
This would likely not have the desired effect as each time Symbol is called it creates a new unique value
Ah yes, that's true. Symbol.for
would fix that, but as you point out, the name you use for the symbol is somewhat irrelevant if you only create the symbol once and export it.
this would result in
@inject("Weapon")
, introducing hidden global dependencies and token collisions.
Yes, there is the potential for token collisions, but on a small project where all interfaces have a unique name, it would work perfectly fine (and InversifyJS makes it really easy to manage, as I've recently discovered 😁).
I can see nameof
being a good fit for dependency injection if you're aware of the limitations. Just like, as someone mentioned earlier in this thread, there would be limitations when using nameof
with minified code. For example, you might use nameof
to get the name of a parameter. During minification, the parameter name is changed, but the compiler-generated "magic string" keeps the original name. Maybe you want the original name, or maybe you want the modified name. As long as you're aware of the limitations that would come with nameof
, you'll be fine.
+1
Here's a couple more hacky workaround functions that work for my purposes so far, which can get the name or path of a nested prop with static checking.
function nameOf<T>(obj: T) {
let name: string | undefined;
const makeCopy = (obj: any): any => {
const copy = {};
for (const key in obj) {
Object.defineProperty(copy, key, {
get() {
name = key;
const value = obj[key];
if (value && typeof value === "object") {
return makeCopy(value);
}
return value;
}
});
}
return copy;
};
return (accessor: { (x: T): any }): string | undefined => {
name = undefined;
accessor(makeCopy(obj));
return name;
};
}
function pathOf<T>(obj: T) {
let path: string[] = [];
const makeCopy = (obj: any): any => {
const copy = {};
for (const key in obj) {
Object.defineProperty(copy, key, {
get() {
path.push(key);
const value = obj[key];
if (value && typeof value === "object") {
return makeCopy(value);
}
return value;
}
});
}
return copy;
};
return (accessor: { (x: T): any }): string[] => {
path = [];
accessor(makeCopy(obj));
return path;
};
}
Example usage:
Why this is expression-level operator when it should be on type-level?
Like this:
type Meme = {}
type S = nameof Meme // NameOf<Meme> perhaps better?
const wrong: S = "wrong" // Type '"wrong"' is not assignable to type '"Meme"'.
const ok: S = "Meme"
@goodmind Why on earth would you want to assign the name of “Meme” (theoretically a string) to a type? That makes no sense. Did you mean “typeof”?
const Why_does_TypeScript_not_have_this_yet: Explanation = { provided: false, reason: null };
console.log(`${nameof(Why_does_TypeScript_not_have_this_yet)}?`);
console.log(Why_does_TypeScript_not_have_this_yet);
if (!Why_does_TypeScript_not_have_this_yet.provided && (new Date()).getFullYear() >= 2018)
this.me.sad = true;
I highly doubt they would ever implement this because nameof
is not in the type namespace and not in any ECMAScript proposals.
That's not a problem though... if #14419 ("Plugin Support for Custom Transformers") were implemented then we could use our own implementations of nameof
, which could be more powerful than a standard nameof
function.
For example, if #14419 were implemented, then we would be able to specify the transformation plugin (example nameof implementation here) in tsconfig.json:
{
"compilerOptions": {
"customTransformers": {
"before": ["node_modules/ts-nameof"]
}
}
}
So overall, I think it makes more sense to ask for #14419 than this issue because it opens the door to nameof and other transformations being easily integrated into a project.
Honestly it would be better if this was straight up closed with "We don't want this, sorry not sorry" then the silence and lack of triage.
ಠ_ಠ
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: