-
Notifications
You must be signed in to change notification settings - Fork 887
New Rule: param-property-in-constructor #1358
Changes from 3 commits
6838d23
203f039
ea9f344
9aa60f3
7a32109
b7a2984
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -0,0 +1,96 @@ | ||||
/** | ||||
* @license | ||||
* Copyright 2014 Palantir Technologies, Inc. | ||||
* | ||||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||||
* you may not use this file except in compliance with the License. | ||||
* You may obtain a copy of the License at | ||||
* | ||||
* http://www.apache.org/licenses/LICENSE-2.0 | ||||
* | ||||
* Unless required by applicable law or agreed to in writing, software | ||||
* distributed under the License is distributed on an "AS IS" BASIS, | ||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
* See the License for the specific language governing permissions and | ||||
* limitations under the License. | ||||
*/ | ||||
|
||||
import * as Lint from "../lint"; | ||||
import * as ts from "typescript"; | ||||
|
||||
export class Rule extends Lint.Rules.AbstractRule { | ||||
/* tslint:disable:object-literal-sort-keys */ | ||||
public static metadata: Lint.IRuleMetadata = { | ||||
ruleName: "no-improper-constructor-param-usage", | ||||
description: "Disallows constructor parameters to be accessed without the 'this.' prefix", | ||||
rationale: Lint.Utils.dedent` | ||||
This helps enforce consistancy. | ||||
When a constructor parameter is accessed without the 'this.' prefix, it is actually not acessing the same thing. | ||||
Example: | ||||
class Foo { | ||||
constructor(public num: number) { | ||||
this.num = 10; | ||||
num = 5; //this should be disallowed! | ||||
} | ||||
} | ||||
const f = new Foo(); | ||||
alert(f.num); // will display 10`, | ||||
optionsDescription: "Not configurable.", | ||||
options: null, | ||||
optionExamples: ["true"], | ||||
type: "functionality", | ||||
}; | ||||
/* tslint:enable:object-literal-sort-keys */ | ||||
|
||||
public static FAILURE_STRING_FACTORY = (paramName: string) => { | ||||
return `The constructor parameter '${paramName}' should only be accessed with the 'this' keyword: 'this.${paramName}'`; | ||||
} | ||||
|
||||
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { | ||||
const languageService = Lint.createLanguageService(sourceFile.fileName, sourceFile.getFullText()); | ||||
return this.applyWithWalker(new NoImproperCtorParamUsageWalker(sourceFile, this.getOptions(), languageService)); | ||||
} | ||||
} | ||||
|
||||
export class NoImproperCtorParamUsageWalker extends Lint.RuleWalker { | ||||
|
||||
private languageService: ts.LanguageService; | ||||
|
||||
constructor(sourceFile: ts.SourceFile, options: Lint.IOptions, languageService: ts.LanguageService) { | ||||
super(sourceFile, options); | ||||
this.languageService = languageService; | ||||
} | ||||
|
||||
public visitConstructorDeclaration(node: ts.ConstructorDeclaration) { | ||||
if (node.parameters && node.parameters.length > 0) { | ||||
const fileName = this.getSourceFile().fileName; | ||||
node.parameters.forEach((param: ts.ParameterDeclaration) => { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Performance and consistency (one iteration style) |
||||
this.validateParam(param, fileName); | ||||
}); | ||||
} | ||||
super.visitConstructorDeclaration(node); | ||||
} | ||||
|
||||
private validateParam(param: ts.ParameterDeclaration, fileName: string) { | ||||
const highlights = this.languageService.getDocumentHighlights(fileName, param.name.getStart(), [fileName]); | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We only need to validate if the parameter is actually a parameter property, right? We check for that in another rule like this: tslint/src/rules/noConstructorVarsRule.ts Line 48 in 81c538c
|
||||
|
||||
if ((highlights !== null && highlights[0].highlightSpans.length > 0)) { | ||||
const paramName = param.name.getText(); | ||||
highlights[0].highlightSpans.forEach((span: ts.HighlightSpan, idx: number) => { | ||||
// Ignore the 1st reference which is the actual parameter definition | ||||
if (idx !== 0 && !this.spanHasThisUsage(span, fileName)) { | ||||
const msg = Rule.FAILURE_STRING_FACTORY(paramName); | ||||
this.addFailure(this.createFailure(span.textSpan.start, span.textSpan.length, msg)); | ||||
} | ||||
}); | ||||
} | ||||
} | ||||
|
||||
private spanHasThisUsage(span: ts.HighlightSpan, fileName: string) { | ||||
const endPos = span.textSpan.start + span.textSpan.length; | ||||
const nameSpanInfo = this.languageService.getNameOrDottedNameSpan(fileName, span.textSpan.start, endPos); | ||||
|
||||
// If the difference between these two is 5 characters, then that account for the missing `this.`! | ||||
return nameSpanInfo.length - span.textSpan.length === 5; | ||||
} | ||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
class FailingExample1 { | ||
constructor(fruit: string, public num: number, private animal: string) { | ||
this.num = 10; | ||
num = 5; | ||
~~~ [The constructor parameter 'num' should only be accessed with the 'this' keyword: 'this.num'] | ||
|
||
animal = "aardvark"; | ||
~~~~~~ [The constructor parameter 'animal' should only be accessed with the 'this' keyword: 'this.animal'] | ||
this.animal = "skunk"; | ||
|
||
fruit = "banana"; | ||
~~~~~ [The constructor parameter 'fruit' should only be accessed with the 'this' keyword: 'this.fruit'] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems like a bug to me, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah good point. i suppose this rule should only check params that have a |
||
this.fruit = "persimmon"; | ||
} | ||
} | ||
|
||
class FailingExample2 { | ||
constructor(thing: Object) { | ||
const wow=thing.doTheThing() | ||
~~~~~ [The constructor parameter 'thing' should only be accessed with the 'this' keyword: 'this.thing'] | ||
console.log(wow,thing.amazingDvdCollection); | ||
~~~~~ [The constructor parameter 'thing' should only be accessed with the 'this' keyword: 'this.thing'] | ||
|
||
const pasta = thing.nested.makePasta(); | ||
~~~~~ [The constructor parameter 'thing' should only be accessed with the 'this' keyword: 'this.thing'] | ||
|
||
thing = {}; | ||
~~~~~ [The constructor parameter 'thing' should only be accessed with the 'this' keyword: 'this.thing'] | ||
|
||
this.thing.doSomethingElse(); | ||
} | ||
} | ||
|
||
class PassingExample1 { | ||
constructor(a: string, private b: string, public c: string) { | ||
this.a = "aaa"; | ||
this.b = "bbb"; | ||
this.c = "ccc"; | ||
} | ||
} | ||
|
||
class PassingExample2 { | ||
constructor(a: Object, private b: Object, public c: Object) { | ||
this.a.doThing(); | ||
this.b.doOtherThing(); | ||
this.c.doLastThing(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"rules": { | ||
"no-improper-constructor-param-usage": true | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could just make this
private languageService: ts.LanguageService
if you want (funny considering what this rule does 😄 )There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lol, ugh why did I not notice this! 😆