Skip to content
This repository has been archived by the owner on Mar 25, 2021. It is now read-only.

no-shadowed-variable: Rewrite and lint for other shadowed names #2598

Merged
merged 10 commits into from Jun 16, 2017
Merged

no-shadowed-variable: Rewrite and lint for other shadowed names #2598

merged 10 commits into from Jun 16, 2017

Conversation

ajafff
Copy link
Contributor

@ajafff ajafff commented Apr 18, 2017

PR checklist

Overview of change:

[enhancement] no-shadowed-variable: added checks for other shadowing declarations, e.g. interfaces, classes, type parameters, imports, etc.
Fixes: #2921
[bugfix] no-shadowed-variable: exempt this parameter
Fixes: #2214
[rule-change] no-shadowed-variable no longer fails for declarations in the same scope, e.g. var foo; var foo;. Use the rule no-duplicate-variable to find such errors.
Refs: #2597

Is there anything you'd like reviewers to focus on?

The compiler already handles declarations in the same scope pretty well. I don't think we should duplicate its logic if declarations are mergeable (or its error messages if they are not). If the user doesn't want declarations to merge, there's still no-duplicate-variable

This is my attempt to simplify #2339.
Closes: #2339
/cc @andy-hanson

interface I {}
interface I {}

// Ignore non-mergeable declarations as the compiler will complain about them anyway
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a compiler error (excepting the unused variable and implicit any) when I use that code. It's perfectly valid to write (function f1() { let f1: any; return f1; })();; the function f1 is shadowed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch. I added some special handling for named function expressions

private addFailureOnIdentifier(ident: ts.Identifier) {
const failureString = Rule.FAILURE_STRING_FACTORY(ident.text);
this.addFailureAtNode(ident, failureString);
function addToList(map: Map<string, ts.Identifier[]>, name: string, identifiers: ts.Identifier[]) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would make this more generic on T instead of ts.Identifier

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding a type parameter doesn't add anything as this function is always called with the same types

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function might need to be used elsewhere (multimaps show up often), but with a different type of values.

~ [err % ('C')]
namespace C {
~ [err % ('C')]
interface C<C> {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now that we're linting for more than just shadowed variables, I'm tempted to change the rule name in the next major release to no-shadowed-name...

for now, can we at least distinguish the failure messages? users might get confused if they see "shadowed variable" for an interface name or a type parameter. let's use Shadowed variable: x for variables and Shadowed name: y for everything else

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adidahiya I changed the error message to always show Shadowed name: x.

Showing a different error message for variables is not really possible:

  • We're not tracking if an identifier is a variable
  • Variables could merge with classes, interfaces, namespaces ... are they still considered a variable
  • And even if we could distinguish the kind of identifiers, it's not clear when to use which message: if a variable shadows a type? if a type shadows a variable? only if a variable shadows another variable?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah good points 👍

@ajafff
Copy link
Contributor Author

ajafff commented Jun 13, 2017

Added handling of imports to address #2921

Copy link
Contributor

@adidahiya adidahiya left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overall lgtm

@@ -198,8 +211,10 @@ function constructorsWithArgsParamConst(
derivedCtor: new (...args: any[]) => any,
baseCtors: Array<new (...args: any[]) => any>,
args: any[]): void {
const args = [];
~~~~ [Shadowed variable: 'args']
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the extra block here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

without the block there would be a compile error because the block scoped variable args cannot merge with the function scoped parameter args.
The block is needed here because the rule only handles shadowed names only in different scopes

~ [err % ('C')]
namespace C {
~ [err % ('C')]
interface C<C> {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah good points 👍

@Martinspire
Copy link

Martinspire commented Jul 10, 2017

So why is this suddenly marking all my classnames as Shadowed Name?

Shadowed name: 'SomeClassName' (no-shadowed-variable)
  37 |      * Some comment.
  38 |      */
> 39 |     export class SomeClassName implements SomeClassToImplement {
     |                 ^
  40 |         /**
  41 |          * Some more comments:


C:/<path>/<file>.ts
Shadowed name: 'SomeOtherClassName' (no-shadowed-variable)
  43 |      * Some comment.
  44 |      */
> 45 |     export class SomeOtherClassName implements SomeClassToImplement {
     |                 ^
  46 |         /**
  47 |          * Some more comments:


C:/<path>/<file>.ts
Shadowed name: 'AnotherClassName' (no-shadowed-variable)
  39 |      * Some comment.
  40 |      */
> 41 |     export class AnotherClassName implements SomeClassToImplement {
     |                 ^
  42 |         /**
  43 |          * Some more comments:

And if you look at how the classes are setup, it is like this:

import Something from "./something";

namespace SomeNamespace {

    /**
     * Comment about class
     */
    export class MyClass {
             <some functions, constructor and variables...>
        }
    }
}

import MyClass = SomeNamespace.MyClass;
export default MyClass;

I don't see how that would be faulty or shadowed
Right now it gives me 431 errors for absolutely no reason.

@ajafff ajafff deleted the no-shadowed-variable branch July 10, 2017 10:30
@ajafff
Copy link
Contributor Author

ajafff commented Jul 10, 2017

@Martinspire import MyClass = SomeNamespace.MyClass; is shadowed by the declaration of MyClass inside the namespace. In your case both point to the same class. The rule does not handle this specific case.

For now you can just export default SomeNamespace.MyClass;.
There's an open issue #3000 that tracks adding options to ignore certain declarations

@Martinspire
Copy link

Martinspire commented Jul 10, 2017

@ajafff but that doesn't work if you have multiple classes and interfaces to export. There can only be one default

Also I just found out that we're using it for Typedoc to separate by namespace so its easier to browse (though I also found a plugin to perhaps do that but, but I don't feel like changing so many files because this rule doesn't allow exclusions)

@Martinspire
Copy link

Martinspire commented Jul 17, 2017

So for example if we want to keep using namespaces for typedoc, we use something like this:


export import ISomeClass = MyNameSpace.ISomeClass1;
export import ISomeClass2 = MyNameSpace.ISomeClass2;
export import ISomeClass3 = MyNameSpace.ISomeClass3;
export import ISomeClass4 = MyNameSpace.ISomeClass4;
import SomeDefaultClass = MyNameSpace.SomeDefaultClass;
export default SomeDefaultClass;

Inside other files I mostly only need SomeDefaultClass, but sometimes I also need the others and its impossible to get the others right now (unless you know another way)
And currently it is complaining, but there is no way to exclude this except for disabling the rule altogether

HyphnKnight pushed a commit to HyphnKnight/tslint that referenced this pull request Apr 9, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
4 participants