-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
[dot-notation] Should typescript-eslint provide a replacement for dot-notation? #789
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
Comments
I'm not quite sure I understand why you would be breaking encapsulation by accessing private internals of a class.
I don't see why
Nope - it should fail if it not a declared member. Typescript will error if it's not a declared member. class X {
private priv_prop = 123;
public pub_prop = 123;
}
const x = new X();
x['doesnt_exist'] = 123; // ts error
x.doesnt_exist = 123; // ts error
x['priv_prop'] = 123;
x.priv_prop = 123; // ts error
x['pub_prop'] = 123;
x.pub_prop = 123; I could see the extension adding an option which goes both ways - class X {
private priv_prop = 123;
}
// allowPrivateClassPropertyAccessViaBracketNotation: true
x['priv_prop'] = 123; // no error
// allowPrivateClassPropertyAccessViaBracketNotation: false
x['priv_prop'] = 123; // error: you should not access private members via bracket notation as it breaks encapsulation |
I'm mostly seeing this in repos that require 100% code coverage for unit tests, so people often resort to hacks to force obscure code paths to get executed. If the recommendation is to spend more time designing the classes to make their contracts more amenable to unit testing... that's noble, but a somewhat unrelated topic from adopting ESLint. :-) Is there a better syntax for allowing unit tests to access private members? Here's what I had considered as an alternative: type PublicMembers<T> = { [P in keyof T]: T[P]; };
class X {
private _a: number = 123;
private _method(): void {
}
public b: number = 123;
}
interface IPrivateX extends PublicMembers<X> {
_a: number;
_method(): void;
}
function unitTest() {
let x: IPrivateX = new X() as any;
console.log(x._a);
console.log(x.b);
x._method();
} This is a LOT more verbose, however. And it might get messy with static members, inheritance, merged declarations, etc.
I see, you must have
The |
100% code coverage is a fallacy and anyone requiring that should be given a good spoken to, IMO :)
sorry, i was just using that as shorthand. const x = Object.create(null); // implicitly typed as any
// there's no difference between these two
x['prop']
x.prop There's no nice way to access private members, because you shouldn't be doing it! But I understand that people can't always spend the time to design the perfect abstractions so you don't access privates, esp when they're wasting a lot of their time trying to get 100% code coverage :P Could extend the rule and add the option as described in my previous comment. |
I agree 100%. 👍 That bandwagon is coming, just a bit further down the road from "convert everything to TypeScript" and "adopt typescript-eslint". |
I chatted with some more people about this. We realized that @bradzacher I am willing to implement your proposed Hmm... is there an example of an extended rule that adds an option to the |
I found an example in no-magic-numbers.ts. |
FYI - these are all the extension rules we have. typescript-eslint/packages/eslint-plugin/tools/generate-configs.ts Lines 21 to 32 in 39e41b5
I've been meaning to write docs on how to do it, but it's not a hugely common thing for people to do. |
@bradzacher I started working on this rule, but I cannot figure out how to access the compiler's semantic analysis. For example: class MyClass {
private _a: number = 123;
public b(): void {
log(this._a.toString());
}
}
const c: MyClass = new MyClass();
log(c['_a'].toString()); Given the |
I'm a bit fuzzy on exactly how it works. It's been a month since I touched typecheck accessing rule code. IIRC, this is something along the lines of what you need to do const parserServices = utils.getParserServices(context);
const typeChecker = program.getTypeChecker();
{
SomeNode(node) {
const tsNode = parserServices.esTreeNodeToTSNodeMap.get<ts.SomeNodeType>(node);
// i can't remember which one you need to use, sorry
const type = typeChecker.getTypeAtLocation(tsNode);
const declaration = util.getDeclaration(typeChecker, tsNode);
},
} |
Awesome, this is what I needed. I'm super familiar with the compiler API, so I can probably figure it out from here. However, I realized that because we already enforce the |
While migrating files, I'm encountering a bunch of warnings for this scenario: interface IFileInfo {
name: string;
}
interface ILookupTable {
[filename: string]: IFileInfo;
}
const table: ILookupTable = { };
table['.gitignore'] = { name: 'first' };
// Error dot-notation: ["LICENSE"] is better written in dot notation.
table['LICENSE'] = { name: 'second' };
// Error dot-notation: ["make"] is better written in dot notation.
table['make'] = { name: 'third' };
for (const key of Object.keys(table)) {
console.log(table[key].name);
} In this example, it would be weird to write Proposal: The new This seems pretty uncontroversial. However, I could make it configurable via a setting, if there are any TypeScript-minded developers out there who actually prefer this sort of inconsistency: // Does anyone prefer this inconsistent form?
table['.gitignore'] = { name: 'first' };
table.LICENSE = { name: 'second' };
table.make = { name: 'third' }; What do you think? |
Could potentially add an option for it, if you want to do the work to implement it. I'm not sure how much use it would see outside of your codebase - I haven't seen the use of |
I encountered it in unit tests that were adding hardcoded entries to the table.
Yeah, I'm willing to implement this when I get some time. |
dot-notation taking into account TS's index signatures would be particular useful in projects where a JS minifier that performs symbol renaming is used. May want to add an option to enforce square-bracket notation for accessing properties on global objects (and objects nested in global objects), and enforce dot-notation otherwise. See: https://developers.google.com/closure/compiler/docs/api-tutorial3#dangers Closure compiler only renames property access by dot notation but not square-bracket notation, which has many implications. |
@LuminescentMoon - that sounds like something you don't need type information for, so it would work fine as an addition to the base eslint rule. ESLint has scope analysis built in by default, so it should be able to tell the difference between a global and an imported variable. |
Repro
Additional Info
ESLint's fixer converts the above code to
x.__tag = 123;
which fails to compile, because__tag
is not a declared member. This is bad, because before migrating a bunch of code to ESLint, you want to run the fixer to clear away any trivial problems, but we certainly don't want the fixer to introduce compiler errors.Also, the expectations in TypeScript might be a little different:
__tag
is a declared member ofX
, then the thedot-notation
warning/fixer makes good sense.__tag
is not a declared member ofX
, then perhaps we should allow this notation, since it's a common idiom used by unit tests to access private members of a class.x
isany
, then[]
notation is maybe okay as well, since it conveys that we're accessing dynamic keys, not declared members.What do you think? Should
typescript-eslint
provide a TypeScript-aware replacement fordot-notation
? If so, should the new rule be called@typescript-eslint/dot-notation
?Versions
@typescript-eslint/eslint-plugin
2.0.0-alpha.4
@typescript-eslint/parser
1.13.0
TypeScript
3.5.3
ESLint
6.1.0
The text was updated successfully, but these errors were encountered: