-
-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
10 changed files
with
539 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
<!-- markdownlint-disable --> | ||
<!-- begin auto-generated rule header --> | ||
|
||
# Disallow inheritance in classes (`functional/no-class-inheritance`) | ||
|
||
💼 This rule is enabled in the following configs: ☑️ `lite`, `noOtherParadigms`, ✅ `recommended`, 🔒 `strict`. | ||
|
||
<!-- end auto-generated rule header --> | ||
<!-- markdownlint-restore --> | ||
<!-- markdownlint-restore --> | ||
|
||
Disallow use of inheritance for classes. | ||
|
||
## Rule Details | ||
|
||
### ❌ Incorrect | ||
|
||
<!-- eslint-skip --> | ||
|
||
```js | ||
/* eslint functional/no-class-inheritance: "error" */ | ||
|
||
abstract class Animal { | ||
constructor(name, age) { | ||
this.name = name; | ||
this.age = age; | ||
} | ||
} | ||
|
||
class Dog extends Animal { | ||
constructor(name, age) { | ||
super(name, age); | ||
} | ||
|
||
get ageInDogYears() { | ||
return 7 * this.age; | ||
} | ||
} | ||
|
||
const dogA = new Dog("Jasper", 2); | ||
|
||
console.log(`${dogA.name} is ${dogA.ageInDogYears} in dog years.`); | ||
``` | ||
|
||
### ✅ Correct | ||
|
||
```js | ||
/* eslint functional/no-class-inheritance: "error" */ | ||
|
||
class Animal { | ||
constructor(name, age) { | ||
this.name = name; | ||
this.age = age; | ||
} | ||
} | ||
|
||
class Dog { | ||
constructor(name, age) { | ||
this.animal = new Animal(name, age); | ||
} | ||
|
||
get ageInDogYears() { | ||
return 7 * this.animal.age; | ||
} | ||
} | ||
|
||
console.log(`${dogA.name} is ${getAgeInDogYears(dogA.age)} in dog years.`); | ||
``` | ||
|
||
## Options | ||
|
||
This rule accepts an options object of the following type: | ||
|
||
```ts | ||
type Options = { | ||
ignoreIdentifierPattern?: string[] | string; | ||
ignoreCodePattern?: string[] | string; | ||
}; | ||
``` | ||
|
||
### Default Options | ||
|
||
```ts | ||
const defaults = {}; | ||
``` | ||
|
||
### `ignoreIdentifierPattern` | ||
|
||
This option takes a RegExp string or an array of RegExp strings. | ||
It allows for the ability to ignore violations based on the class's name. | ||
|
||
### `ignoreCodePattern` | ||
|
||
This option takes a RegExp string or an array of RegExp strings. | ||
It allows for the ability to ignore violations based on the code itself. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
import type { JSONSchema4 } from "@typescript-eslint/utils/json-schema"; | ||
import type { RuleContext } from "@typescript-eslint/utils/ts-eslint"; | ||
import { deepmerge } from "deepmerge-ts"; | ||
|
||
import { | ||
type IgnoreCodePatternOption, | ||
type IgnoreIdentifierPatternOption, | ||
ignoreCodePatternOptionSchema, | ||
ignoreIdentifierPatternOptionSchema, | ||
shouldIgnorePattern, | ||
} from "#/options"; | ||
import { ruleNameScope } from "#/utils/misc"; | ||
import type { ESClass } from "#/utils/node-types"; | ||
import { | ||
type NamedCreateRuleCustomMeta, | ||
type Rule, | ||
type RuleResult, | ||
createRule, | ||
} from "#/utils/rule"; | ||
|
||
/** | ||
* The name of this rule. | ||
*/ | ||
export const name = "no-class-inheritance"; | ||
|
||
/** | ||
* The full name of this rule. | ||
*/ | ||
export const fullName: `${typeof ruleNameScope}/${typeof name}` = `${ruleNameScope}/${name}`; | ||
|
||
/** | ||
* The options this rule can take. | ||
*/ | ||
type Options = [IgnoreIdentifierPatternOption & IgnoreCodePatternOption]; | ||
|
||
/** | ||
* The schema for the rule options. | ||
*/ | ||
const schema: JSONSchema4[] = [ | ||
{ | ||
type: "object", | ||
properties: deepmerge( | ||
ignoreIdentifierPatternOptionSchema, | ||
ignoreCodePatternOptionSchema, | ||
), | ||
additionalProperties: false, | ||
}, | ||
]; | ||
|
||
/** | ||
* The default options for the rule. | ||
*/ | ||
const defaultOptions: Options = [{}]; | ||
|
||
/** | ||
* The possible error messages. | ||
*/ | ||
const errorMessages = { | ||
abstract: "Unexpected abstract class.", | ||
extends: "Unexpected inheritance, use composition instead.", | ||
} as const; | ||
|
||
/** | ||
* The meta data for this rule. | ||
*/ | ||
const meta: NamedCreateRuleCustomMeta<keyof typeof errorMessages> = { | ||
type: "suggestion", | ||
docs: { | ||
category: "No Other Paradigms", | ||
description: "Disallow inheritance in classes.", | ||
recommended: "recommended", | ||
recommendedSeverity: "error", | ||
requiresTypeChecking: false, | ||
}, | ||
messages: errorMessages, | ||
schema, | ||
}; | ||
|
||
/** | ||
* Check if the given class node violates this rule. | ||
*/ | ||
function checkClass( | ||
node: ESClass, | ||
context: Readonly<RuleContext<keyof typeof errorMessages, Options>>, | ||
options: Readonly<Options>, | ||
): RuleResult<keyof typeof errorMessages, Options> { | ||
const [optionsObject] = options; | ||
const { ignoreIdentifierPattern, ignoreCodePattern } = optionsObject; | ||
|
||
const m_descriptors: Array< | ||
RuleResult<keyof typeof errorMessages, Options>["descriptors"][number] | ||
> = []; | ||
|
||
if ( | ||
!shouldIgnorePattern( | ||
node, | ||
context, | ||
ignoreIdentifierPattern, | ||
undefined, | ||
ignoreCodePattern, | ||
) | ||
) { | ||
if (node.abstract) { | ||
const nodeText = context.sourceCode.getText(node); | ||
const abstractRelativeIndex = nodeText.indexOf("abstract"); | ||
const abstractIndex = | ||
context.sourceCode.getIndexFromLoc(node.loc.start) + | ||
abstractRelativeIndex; | ||
const start = context.sourceCode.getLocFromIndex(abstractIndex); | ||
const end = context.sourceCode.getLocFromIndex( | ||
abstractIndex + "abstract".length, | ||
); | ||
|
||
m_descriptors.push({ | ||
node, | ||
loc: { | ||
start, | ||
end, | ||
}, | ||
messageId: "abstract", | ||
}); | ||
} | ||
|
||
if (node.superClass !== null) { | ||
const nodeText = context.sourceCode.getText(node); | ||
const extendsRelativeIndex = nodeText.indexOf("extends"); | ||
const extendsIndex = | ||
context.sourceCode.getIndexFromLoc(node.loc.start) + | ||
extendsRelativeIndex; | ||
const start = context.sourceCode.getLocFromIndex(extendsIndex); | ||
const { end } = node.superClass.loc; | ||
|
||
m_descriptors.push({ | ||
node, | ||
loc: { | ||
start, | ||
end, | ||
}, | ||
messageId: "extends", | ||
}); | ||
} | ||
} | ||
|
||
return { | ||
context, | ||
descriptors: m_descriptors, | ||
}; | ||
} | ||
|
||
// Create the rule. | ||
export const rule: Rule<keyof typeof errorMessages, Options> = createRule< | ||
keyof typeof errorMessages, | ||
Options | ||
>(name, meta, defaultOptions, { | ||
ClassDeclaration: checkClass, | ||
ClassExpression: checkClass, | ||
}); |
Oops, something went wrong.