Files
dspace-angular/lint/src/util/fix.ts
Yury Bondarenko e40b6ae612 Update plugins to support standalone components
- ThemedComponent wrappers should always import their base component. This ensures that it's always enough to only import the wrapper when we use it.
- This implies that all themeable components must be standalone

→ added rules to enforce this
→ updated usage rule to improve declaration/import handling
2024-03-28 18:33:46 +01:00

126 lines
4.4 KiB
TypeScript

/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
import { TSESTree } from '@typescript-eslint/utils';
import {
RuleContext,
RuleFix,
RuleFixer,
} from '@typescript-eslint/utils/ts-eslint';
import { getSourceCode } from './typescript';
export function appendObjectProperties(context: RuleContext<any, any>, fixer: RuleFixer, objectNode: TSESTree.ObjectExpression, properties: string[]): RuleFix {
// todo: may not handle empty objects too well
const lastProperty = objectNode.properties[objectNode.properties.length - 1];
const source = getSourceCode(context);
const nextToken = source.getTokenAfter(lastProperty);
// todo: newline & indentation are hardcoded for @Component({})
// todo: we're assuming that we need trailing commas, what if we don't?
const newPart = '\n' + properties.map(p => ` ${p},`).join('\n');
if (nextToken !== null && nextToken.value === ',') {
return fixer.insertTextAfter(nextToken, newPart);
} else {
return fixer.insertTextAfter(lastProperty, ',' + newPart);
}
}
export function appendArrayElement(context: RuleContext<any, any>, fixer: RuleFixer, arrayNode: TSESTree.ArrayExpression, value: string): RuleFix {
const source = getSourceCode(context);
if (arrayNode.elements.length === 0) {
// This is the first element
const openArray = source.getTokenByRangeStart(arrayNode.range[0]);
if (openArray == null) {
throw new Error('Unexpected null token for opening square bracket');
}
// safe to assume the list is single-line
return fixer.insertTextAfter(openArray, `${value}`);
} else {
const lastElement = arrayNode.elements[arrayNode.elements.length - 1];
if (lastElement == null) {
throw new Error('Unexpected null node in array');
}
const nextToken = source.getTokenAfter(lastElement);
// todo: we don't know if the list is chopped or not, so we can't make any assumptions -- may produce output that will be flagged by other rules on the next run!
// todo: we're assuming that we need trailing commas, what if we don't?
if (nextToken !== null && nextToken.value === ',') {
return fixer.insertTextAfter(nextToken, ` ${value},`);
} else {
return fixer.insertTextAfter(lastElement, `, ${value},`);
}
}
}
export function isLast(elementNode: TSESTree.Node): boolean {
if (!elementNode.parent) {
return false;
}
let siblingNodes: (TSESTree.Node | null)[] = [null];
if (elementNode.parent.type === TSESTree.AST_NODE_TYPES.ArrayExpression) {
siblingNodes = elementNode.parent.elements;
} else if (elementNode.parent.type === TSESTree.AST_NODE_TYPES.ImportDeclaration) {
siblingNodes = elementNode.parent.specifiers;
}
return elementNode === siblingNodes[siblingNodes.length - 1];
}
export function removeWithCommas(context: RuleContext<any, any>, fixer: RuleFixer, elementNode: TSESTree.Node): RuleFix[] {
const ops = [];
const source = getSourceCode(context);
let nextToken = source.getTokenAfter(elementNode);
let prevToken = source.getTokenBefore(elementNode);
if (nextToken !== null && prevToken !== null) {
if (nextToken.value === ',') {
nextToken = source.getTokenAfter(nextToken);
if (nextToken !== null) {
ops.push(fixer.removeRange([elementNode.range[0], nextToken.range[0]]));
}
}
if (isLast(elementNode) && prevToken.value === ',') {
prevToken = source.getTokenBefore(prevToken);
if (prevToken !== null) {
ops.push(fixer.removeRange([prevToken.range[1], elementNode.range[1]]));
}
}
} else if (nextToken !== null) {
ops.push(fixer.removeRange([elementNode.range[0], nextToken.range[0]]));
}
return ops;
}
export function replaceOrRemoveArrayIdentifier(context: RuleContext<any, any>, fixer: RuleFixer, identifierNode: TSESTree.Identifier, newValue: string): RuleFix[] {
if (identifierNode.parent.type !== TSESTree.AST_NODE_TYPES.ArrayExpression) {
throw new Error('Parent node is not an array expression!');
}
const array = identifierNode.parent as TSESTree.ArrayExpression;
for (const element of array.elements) {
if (element !== null && element.type === TSESTree.AST_NODE_TYPES.Identifier && element.name === newValue) {
return removeWithCommas(context, fixer, identifierNode);
}
}
return [fixer.replaceText(identifierNode, newValue)];
}