typescript-eslint/no this alias (#526)

Co-authored-by: eli lichtblau <elilichtblau@elis-MacBook-Pro.local>
This commit is contained in:
EliLichtblau 2023-07-09 04:22:38 -04:00 committed by GitHub
parent cdaff8b7f1
commit fa183f6a4b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 337 additions and 1 deletions

View file

@ -59,7 +59,8 @@ oxc_macros::declare_all_lint_rules! {
typescript::no_extra_non_null_assertion,
typescript::no_non_null_asserted_optional_chain,
typescript::no_unnecessary_type_constraint,
typescript::no_misused_new
typescript::no_misused_new,
typescript::no_this_alias
}
#[cfg(test)]

View file

@ -0,0 +1,213 @@
use oxc_ast::{
ast::{AssignmentTarget, BindingPatternKind, Expression, SimpleAssignmentTarget},
AstKind,
};
use oxc_diagnostics::{
miette::{self, Diagnostic},
thiserror::Error,
};
use oxc_macros::declare_oxc_lint;
use oxc_span::{GetSpan, Span, Atom};
use crate::{context::LintContext, rule::Rule, AstNode};
#[derive(Debug, Error, Diagnostic)]
#[error("typescript-eslint(no-this alias): Unexpected aliasing of 'this' to local variable.")]
#[diagnostic(severity(error), help("Assigning a variable to this instead of properly using arrow lambdas may be a symptom of pre-ES6 practices or not managing scope well."))]
struct NoThisAliasDiagnostic(#[label] pub Span);
#[derive(Debug, Error, Diagnostic)]
#[error(
"typescript-eslint(no-this alias): Unexpected aliasing of members of 'this' to local variables."
)]
#[diagnostic(severity(error), help("Disabling destructuring of this is not a default, consider allowing destructuring"))]
struct NoThisDestructureDiagnostic(#[label] pub Span);
#[derive(Debug, Clone)]
pub struct NoThisAlias {
allow_destructuring: bool,
allow_names: Vec<Atom>,
}
impl Default for NoThisAlias {
fn default() -> Self {
Self { allow_destructuring: true, allow_names: vec![] }
}
}
declare_oxc_lint!(
/// ### What it does
///
/// Disallow unnecessary constraints on generic types.
///
/// ### Why is this bad?
///
/// Generic type parameters (<T>) in TypeScript may be "constrained" with an extends keyword.
/// When no extends is provided, type parameters default a constraint to unknown. It is therefore redundant to extend from any or unknown.
///
/// the rule doesn't allow const {allowedName} = this
/// this is to keep 1:1 with eslint implementation
/// sampe with obj.<allowedName> = this
/// ```
NoThisAlias,
correctness
);
impl Rule for NoThisAlias {
fn from_configuration(value: serde_json::Value) -> Self {
let obj = value.get(0);
let allowed_names = value
.get(0)
.and_then(|v| v.get("allow_names"))
.and_then(serde_json::Value::as_array)
.unwrap_or(&vec![])
.iter()
.map(serde_json::Value::as_str)
.filter(std::option::Option::is_some)
.map(|x| Atom::from(x.unwrap().to_string()))
.collect::<Vec<Atom>>();
Self {
allow_destructuring: obj
.and_then(|v| v.get("allow_destructuring"))
.and_then(serde_json::Value::as_bool)
.unwrap_or_default(),
allow_names: allowed_names,
}
}
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
match node.kind() {
AstKind::VariableDeclarator(decl) => {
if decl.init.is_none() {
return;
}
if let Some(init) = &decl.init && !rhs_is_this_reference(init) {
return;
}
if self.allow_destructuring
&& !matches!(decl.id.kind, BindingPatternKind::BindingIdentifier(_))
{
return;
}
if let BindingPatternKind::BindingIdentifier(identifier) = &decl.id.kind {
if !self.allow_names.contains(&identifier.name) {
ctx.diagnostic(NoThisAliasDiagnostic(identifier.span));
}
return;
}
ctx.diagnostic(NoThisDestructureDiagnostic(decl.id.kind.span()));
}
AstKind::AssignmentExpression(assignment) => {
if !rhs_is_this_reference(&assignment.right) {
return;
}
match &assignment.left {
AssignmentTarget::AssignmentTargetPattern(pat) => {
if self.allow_destructuring {
return;
}
ctx.diagnostic(NoThisDestructureDiagnostic(pat.span()));
}
AssignmentTarget::SimpleAssignmentTarget(pat) => match pat {
SimpleAssignmentTarget::AssignmentTargetIdentifier(id) => {
if !self.allow_names.contains(&id.name) {
ctx.diagnostic(NoThisAliasDiagnostic(id.span));
}
}
_ => {
if let Some(expr) = pat.get_expression() {
if let Some(id) = expr.get_identifier_reference() {
if !self.allow_names.contains(&id.name) {
ctx.diagnostic(NoThisAliasDiagnostic(id.span));
}
}
}
}
},
}
}
_ => {}
}
}
}
fn rhs_is_this_reference(rhs_expression: &Expression) -> bool {
matches!(rhs_expression, Expression::ThisExpression(_))
}
#[test]
fn test() {
use crate::tester::Tester;
let pass = vec![
// allow destructuring
(
"const { props, state } = this;",
Some(serde_json::json!([{ "allow_destructuring": true }])),
),
("const { length } = this;", Some(serde_json::json!([{ "allow_destructuring": true }]))),
(
"const { length, toString } = this;",
Some(serde_json::json!([{ "allow_destructuring": true }])),
),
("const [foo] = this;", Some(serde_json::json!([{ "allow_destructuring": true }]))),
("const [foo, bar] = this;", Some(serde_json::json!([{ "allow_destructuring": true }]))),
// allow list
("const self = this;", Some(serde_json::json!([{ "allow_names": vec!["self"] }]))),
];
let fail = vec![
("const self = this;", None),
(
"const { props, state } = this;",
Some(serde_json::json!([{ "allow_destructuring": false }])),
),
(
"const [ props, state ] = this;",
Some(serde_json::json!([{ "allow_destructuring": false }])),
),
("let foo; \nconst other =3;\n\n\n\nfoo = this", None),
("let foo; (foo as any) = this", None),
(
"function testFunction() {
let inFunction = this;
}",
None,
),
(
"const testLambda = () => {
const inLambda = this;
};",
None,
),
// this slightly modified because conflicting ID's wont compile
(
"class TestClass {
constructor() {
const inConstructor = this;
const asThis: this = this;
const asString = 'this';
const asArray = [this];
const asArrayString = ['this'];
}
public act(scope: this = this) {
const inMemberFunction = this;
const { act1 } = this;
const { act2, constructor } = this;
const [foo1] = this;
const [foo, bar] = this;
}
}",
Some(serde_json::json!([{ "allow_destructuring": false }])),
),
];
Tester::new(NoThisAlias::NAME, pass, fail).test_and_snapshot();
}

View file

@ -0,0 +1,122 @@
---
source: crates/oxc_linter/src/tester.rs
expression: no_this_alias
---
× typescript-eslint(no-this alias): Unexpected aliasing of 'this' to local variable.
╭─[no_this_alias.tsx:1:1]
1 │ const self = this;
· ────
╰────
help: Assigning a variable to this instead of properly using arrow lambdas may be a symptom of pre-ES6 practices or not managing scope well.
× typescript-eslint(no-this alias): Unexpected aliasing of members of 'this' to local variables.
╭─[no_this_alias.tsx:1:1]
1 │ const { props, state } = this;
· ────────────────
╰────
help: Disabling destructuring of this is not a default, consider allowing destructuring
× typescript-eslint(no-this alias): Unexpected aliasing of members of 'this' to local variables.
╭─[no_this_alias.tsx:1:1]
1 │ const [ props, state ] = this;
· ────────────────
╰────
help: Disabling destructuring of this is not a default, consider allowing destructuring
× typescript-eslint(no-this alias): Unexpected aliasing of 'this' to local variable.
╭─[no_this_alias.tsx:5:1]
5 │
6 │ foo = this
· ───
╰────
help: Assigning a variable to this instead of properly using arrow lambdas may be a symptom of pre-ES6 practices or not managing scope well.
× typescript-eslint(no-this alias): Unexpected aliasing of 'this' to local variable.
╭─[no_this_alias.tsx:1:1]
1 │ let foo; (foo as any) = this
· ───
╰────
help: Assigning a variable to this instead of properly using arrow lambdas may be a symptom of pre-ES6 practices or not managing scope well.
× typescript-eslint(no-this alias): Unexpected aliasing of 'this' to local variable.
╭─[no_this_alias.tsx:1:1]
1 │ function testFunction() {
2 │ let inFunction = this;
· ──────────
3 │ }
╰────
help: Assigning a variable to this instead of properly using arrow lambdas may be a symptom of pre-ES6 practices or not managing scope well.
× typescript-eslint(no-this alias): Unexpected aliasing of 'this' to local variable.
╭─[no_this_alias.tsx:1:1]
1 │ const testLambda = () => {
2 │ const inLambda = this;
· ────────
3 │ };
╰────
help: Assigning a variable to this instead of properly using arrow lambdas may be a symptom of pre-ES6 practices or not managing scope well.
× typescript-eslint(no-this alias): Unexpected aliasing of 'this' to local variable.
╭─[no_this_alias.tsx:2:1]
2 │ constructor() {
3 │ const inConstructor = this;
· ─────────────
4 │ const asThis: this = this;
╰────
help: Assigning a variable to this instead of properly using arrow lambdas may be a symptom of pre-ES6 practices or not managing scope well.
× typescript-eslint(no-this alias): Unexpected aliasing of 'this' to local variable.
╭─[no_this_alias.tsx:3:1]
3 │ const inConstructor = this;
4 │ const asThis: this = this;
· ──────
5 │
╰────
help: Assigning a variable to this instead of properly using arrow lambdas may be a symptom of pre-ES6 practices or not managing scope well.
× typescript-eslint(no-this alias): Unexpected aliasing of 'this' to local variable.
╭─[no_this_alias.tsx:11:1]
11 │ public act(scope: this = this) {
12 │ const inMemberFunction = this;
· ────────────────
13 │ const { act1 } = this;
╰────
help: Assigning a variable to this instead of properly using arrow lambdas may be a symptom of pre-ES6 practices or not managing scope well.
× typescript-eslint(no-this alias): Unexpected aliasing of members of 'this' to local variables.
╭─[no_this_alias.tsx:12:1]
12 │ const inMemberFunction = this;
13 │ const { act1 } = this;
· ────────
14 │ const { act2, constructor } = this;
╰────
help: Disabling destructuring of this is not a default, consider allowing destructuring
× typescript-eslint(no-this alias): Unexpected aliasing of members of 'this' to local variables.
╭─[no_this_alias.tsx:13:1]
13 │ const { act1 } = this;
14 │ const { act2, constructor } = this;
· ─────────────────────
15 │ const [foo1] = this;
╰────
help: Disabling destructuring of this is not a default, consider allowing destructuring
× typescript-eslint(no-this alias): Unexpected aliasing of members of 'this' to local variables.
╭─[no_this_alias.tsx:14:1]
14 │ const { act2, constructor } = this;
15 │ const [foo1] = this;
· ──────
16 │ const [foo, bar] = this;
╰────
help: Disabling destructuring of this is not a default, consider allowing destructuring
× typescript-eslint(no-this alias): Unexpected aliasing of members of 'this' to local variables.
╭─[no_this_alias.tsx:15:1]
15 │ const [foo1] = this;
16 │ const [foo, bar] = this;
· ──────────
17 │ }
╰────
help: Disabling destructuring of this is not a default, consider allowing destructuring