Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve S6478 (no-unstable-nested-components): Replace message and refine location #4323

Merged
merged 1 commit into from Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions its/ruling/src/test/expected/jsts/eigen/typescript-S6478.json
Expand Up @@ -35,8 +35,8 @@
62
],
"eigen:src/app/Components/ReadMore.tsx": [
59,
75
63,
79
],
"eigen:src/app/Components/RetryErrorBoundary.tsx": [
39
Expand Down
43 changes: 14 additions & 29 deletions packages/jsts/src/rules/S6478/cb.fixture.jsx
@@ -1,41 +1,26 @@
function Component() {
function UnstableNestedComponent() { // Noncompliant {{Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state. Instead, move this component definition out of the parent component “Component” and pass data as props.}}
function Component1() {
function NestedComponent1() { // Noncompliant {{Move this component definition out of the parent component and pass data as props.}}
// ^^^^^^^^^^^^^^^^
return <div />;
}
return (
<div>
<UnstableNestedComponent />
<NestedComponent1 />
</div>
);
}

function SomeComponent({ footer: Footer }) {
return (
<div>
<Footer />
</div>
);
}

function Component() {
return (
<div>
<SomeComponent footer={ () => <div /> } /> { /* Noncompliant {{Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state. Instead, move this component definition out of the parent component “Component” and pass data as props. If you want to allow component creation in props, set allowAsProps option to true.}} */ }
</div>
);
}

class Component extends React.Component {
render() {
function UnstableNestedComponent() { { /* Noncompliant {{Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state. Instead, move this component definition out of the parent component “Component” and pass data as props.}} */ }
function Component2() {
class NestedComponent2 extends React.Component { // Noncompliant
// ^^^^^^^^^^^^^^^^
render() {
return <div />;
}
};

return (
<div>
<UnstableNestedComponent />
</div>
);
}
return (
<div>
<NestedComponent2 />
</div>
);
}

47 changes: 33 additions & 14 deletions packages/jsts/src/rules/S6478/decorator.ts
Expand Up @@ -20,23 +20,42 @@
// https://sonarsource.github.io/rspec/#/rspec/S6478/javascript

import { Rule } from 'eslint';
import { interceptReportForReact } from '../helpers';
import * as estree from 'estree';
import { RuleContext, functionLike, interceptReportForReact } from '../helpers';
import { getMainFunctionTokenLocation } from 'eslint-plugin-sonarjs/lib/utils/locations';
import { TSESTree } from '@typescript-eslint/experimental-utils';

export function decorate(rule: Rule.RuleModule): Rule.RuleModule {
return interceptReportForReact(rule, changeMessageWith(urlRemover()));
}
return interceptReportForReact(rule, (context, report) => {
const message =
'Move this component definition out of the parent component and pass data as props.';
const { node } = report as { node: estree.Node };
const loc = getMainNodeLocation(node, context);
if (loc) {
context.report({ ...report, loc, message });
} else {
context.report({ ...report, message });
}
});

function changeMessageWith(messageChanger: (message: string) => string) {
return (context: Rule.RuleContext, reportDescriptor: Rule.ReportDescriptor) => {
const report = reportDescriptor as { message?: string };
if (report.message) {
report.message = messageChanger(report.message);
function getMainNodeLocation(node: estree.Node, context: Rule.RuleContext) {
/* class components */
if (node.type === 'ClassDeclaration' || node.type === 'ClassExpression') {
if (node.id) {
return node.id.loc;
} else {
return context.sourceCode.getFirstToken(node, token => token.value === 'class')?.loc;
}
}

/* functional components */
if (functionLike.has(node.type)) {
const fun = node as unknown as TSESTree.FunctionLike;
const ctx = context as unknown as RuleContext;
return getMainFunctionTokenLocation(fun, fun.parent, ctx) as estree.SourceLocation;
}
context.report(reportDescriptor);
};
}

function urlRemover() {
const urlRegexp = / \(https:[^)]+\)/;
return (message: string) => message.replace(urlRegexp, '');
/* should not happen */
return node.loc;
}
}