📄 parameter-properties¶
📊 Analysis Summary¶
| Metric | Count |
|---|---|
| 🔧 Functions | 4 |
| 📦 Imports | 4 |
| 📐 Interfaces | 1 |
| 📑 Type Aliases | 4 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 packages/eslint-plugin/src/rules/parameter-properties.ts
📤 Default Export¶
| Property | Value |
|---|---|
name |
'parameter-properties' |
meta.type |
'problem' |
meta.docs.description |
'Require or disallow parameter properties in class constructors' |
meta.messages.preferClassProperty |
'Property {{parameter}} should be declared as a class property.' |
meta.messages.preferParameterProperty |
'Property {{parameter}} should be declared as a parameter property.' |
meta.schema |
[ { type: 'object', $defs: { modifier: { type: 'string', enum: [ 'readonly', 'private', 'protected', 'public', 'priva... |
defaultOptions |
[ { allow: [], prefer: 'class-property', }, ] |
Entry point: create — documented under Functions.
📦 Imports¶
| Name | Source |
|---|---|
TSESTree |
@typescript-eslint/utils |
AST_NODE_TYPES |
@typescript-eslint/utils |
createRule |
../util |
nullThrows |
../util |
Functions¶
create(context: any, [{ allow = [], prefer = 'class-…: any): { TSParameterProperty(node: any): void; ':matches(ClassDecl…¶
Parameters:
contextany[{ allow = [], prefer = 'class-property' }]any
Returns: { TSParameterProperty(node: any): void; ':matches(ClassDeclaration, ClassExpression):exit'?: undefined; ClassBody?: undefined; 'ClassDeclaration, ClassExpression'?: undefined; 'MethodDefinition[kind="constructor"]'?: undefined; } | { ':matches(ClassDeclaration, ClassExpression):exit'(): void; ClassBody(node: any): void; 'ClassDeclaration, ClassExpression'(): void; 'MethodDefinition[kind="constructor"]'(node: TSESTree.MethodDefinition): void; TSParameterProperty?: undefined; }
Calls:
modifiers.pushmodifiers.filter(Boolean).joingetModifiersallow.includescontext.reportpropertyNodesByName.getpropertyNodesByName.setcontext.sourceCode.getTextnullThrows (from ../util)propertyNodesByNameStack.poptypeAnnotationsMatchgetNodesByNamepropertyNodesByNameStack.push
Internal Comments:
Code
create(context, [{ allow = [], prefer = 'class-property' }]) {
/**
* Gets the modifiers of `node`.
* @param node the node to be inspected.
*/
function getModifiers(
node: TSESTree.PropertyDefinition | TSESTree.TSParameterProperty,
): Modifier {
const modifiers: Modifier[] = [];
if (node.accessibility) {
modifiers.push(node.accessibility);
}
if (node.readonly) {
modifiers.push('readonly');
}
return modifiers.filter(Boolean).join(' ') as Modifier;
}
if (prefer === 'class-property') {
return {
TSParameterProperty(node): void {
const modifiers = getModifiers(node);
if (!allow.includes(modifiers)) {
const name =
node.parameter.type === AST_NODE_TYPES.Identifier
? node.parameter.name
: node.parameter.left.name;
context.report({
node,
messageId: 'preferClassProperty',
data: {
parameter: name,
},
});
}
},
};
}
interface PropertyNodes {
classProperty?: TSESTree.PropertyDefinition;
constructorAssignment?: TSESTree.AssignmentExpression;
constructorParameter?: TSESTree.Identifier;
}
const propertyNodesByNameStack: Map<string, PropertyNodes>[] = [];
function getNodesByName(name: string): PropertyNodes {
const propertyNodesByName =
propertyNodesByNameStack[propertyNodesByNameStack.length - 1];
const existing = propertyNodesByName.get(name);
if (existing) {
return existing;
}
const created: PropertyNodes = {};
propertyNodesByName.set(name, created);
return created;
}
function typeAnnotationsMatch(
classProperty: TSESTree.PropertyDefinition,
constructorParameter: TSESTree.Identifier,
): boolean {
if (
!classProperty.typeAnnotation ||
!constructorParameter.typeAnnotation
) {
return (
classProperty.typeAnnotation === constructorParameter.typeAnnotation
);
}
return (
context.sourceCode.getText(classProperty.typeAnnotation) ===
context.sourceCode.getText(constructorParameter.typeAnnotation)
);
}
return {
':matches(ClassDeclaration, ClassExpression):exit'(): void {
const propertyNodesByName = nullThrows(
propertyNodesByNameStack.pop(),
'Stack should exist on class exit',
);
for (const [name, nodes] of propertyNodesByName) {
if (
nodes.classProperty &&
nodes.constructorAssignment &&
nodes.constructorParameter &&
typeAnnotationsMatch(
nodes.classProperty,
nodes.constructorParameter,
)
) {
context.report({
node: nodes.classProperty,
messageId: 'preferParameterProperty',
data: {
parameter: name,
},
});
}
}
},
ClassBody(node): void {
for (const element of node.body) {
if (
element.type === AST_NODE_TYPES.PropertyDefinition &&
element.key.type === AST_NODE_TYPES.Identifier &&
!element.value &&
!allow.includes(getModifiers(element))
) {
getNodesByName(element.key.name).classProperty = element;
}
}
},
'ClassDeclaration, ClassExpression'(): void {
propertyNodesByNameStack.push(new Map());
},
'MethodDefinition[kind="constructor"]'(
node: TSESTree.MethodDefinition,
): void {
for (const parameter of node.value.params) {
if (parameter.type === AST_NODE_TYPES.Identifier) {
getNodesByName(parameter.name).constructorParameter = parameter;
}
}
for (const statement of node.value.body?.body ?? []) {
if (
statement.type !== AST_NODE_TYPES.ExpressionStatement ||
statement.expression.type !== AST_NODE_TYPES.AssignmentExpression ||
statement.expression.left.type !==
AST_NODE_TYPES.MemberExpression ||
statement.expression.left.object.type !==
AST_NODE_TYPES.ThisExpression ||
statement.expression.left.property.type !==
AST_NODE_TYPES.Identifier ||
statement.expression.right.type !== AST_NODE_TYPES.Identifier
) {
break;
}
getNodesByName(
statement.expression.right.name,
).constructorAssignment = statement.expression;
}
},
};
}
Internal helpers¶
Declared inside another function in this file.
getModifiers(node: TSESTree.PropertyDefinition | TSESTree.…): Modifier¶
Gets the modifiers of node.
Parameters:
nodeany: the node to be inspected.
Calls:
modifiers.pushmodifiers.filter(Boolean).join
Code
function getModifiers(
node: TSESTree.PropertyDefinition | TSESTree.TSParameterProperty,
): Modifier {
const modifiers: Modifier[] = [];
if (node.accessibility) {
modifiers.push(node.accessibility);
}
if (node.readonly) {
modifiers.push('readonly');
}
return modifiers.filter(Boolean).join(' ') as Modifier;
}
getNodesByName(name: string): PropertyNodes¶
Parameters:
namestring
Returns: PropertyNodes
Calls:
propertyNodesByName.getpropertyNodesByName.set
Code
function getNodesByName(name: string): PropertyNodes {
const propertyNodesByName =
propertyNodesByNameStack[propertyNodesByNameStack.length - 1];
const existing = propertyNodesByName.get(name);
if (existing) {
return existing;
}
const created: PropertyNodes = {};
propertyNodesByName.set(name, created);
return created;
}
typeAnnotationsMatch(classProperty: TSESTree.PropertyDefinition, constructorParameter: TSESTree.Identifier): boolean¶
Parameters:
classPropertyTSESTree.PropertyDefinitionconstructorParameterTSESTree.Identifier
Returns: boolean
Calls:
context.sourceCode.getText
Code
function typeAnnotationsMatch(
classProperty: TSESTree.PropertyDefinition,
constructorParameter: TSESTree.Identifier,
): boolean {
if (
!classProperty.typeAnnotation ||
!constructorParameter.typeAnnotation
) {
return (
classProperty.typeAnnotation === constructorParameter.typeAnnotation
);
}
return (
context.sourceCode.getText(classProperty.typeAnnotation) ===
context.sourceCode.getText(constructorParameter.typeAnnotation)
);
}
Interfaces¶
PropertyNodes¶
Interface Code
Properties¶
| Name | Type | Optional | Description |
|---|---|---|---|
classProperty |
TSESTree.PropertyDefinition |
✓ | not shown |
constructorAssignment |
TSESTree.AssignmentExpression |
✓ | not shown |
constructorParameter |
TSESTree.Identifier |
✓ | not shown |
Type Aliases¶
Modifier¶
type Modifier = | 'private'
| 'private readonly'
| 'protected'
| 'protected readonly'
| 'public'
| 'public readonly'
| 'readonly';
Prefer¶
Options¶
MessageIds¶
Generated by Syntax Scribe