2020-06-30 09:22:25 +02:00
|
|
|
import React from 'react';
|
2020-07-09 18:53:51 +02:00
|
|
|
|
|
|
|
import { makeStyles } from '@material-ui/core/styles';
|
2020-06-29 23:43:49 +02:00
|
|
|
import Container from '@material-ui/core/Container';
|
|
|
|
import Typography from '@material-ui/core/Typography';
|
2020-06-30 14:19:29 +02:00
|
|
|
import Tabs from '@material-ui/core/Tabs';
|
|
|
|
import Tab from '@material-ui/core/Tab';
|
2020-07-09 18:53:51 +02:00
|
|
|
import Box from '@material-ui/core/Box';
|
2022-01-28 15:17:25 +01:00
|
|
|
import { createMuiTheme, Link, ThemeProvider } from '@material-ui/core';
|
2021-09-30 17:15:45 +02:00
|
|
|
import Highlight from 'react-highlight';
|
2021-10-01 15:47:43 +02:00
|
|
|
import { Link as RouterLink, useHistory } from 'react-router-dom';
|
|
|
|
import { RULE_STATE, useRuleCoverage } from './utils/useRuleCoverage';
|
2020-07-09 18:53:51 +02:00
|
|
|
import { useFetch } from './utils/useFetch';
|
2021-01-26 22:10:28 +01:00
|
|
|
import { RuleMetadata } from './types';
|
2022-02-07 19:16:41 +01:00
|
|
|
import parse, { attributesToProps, domToReact, DOMNode, Element } from 'html-react-parser';
|
2020-07-09 18:53:51 +02:00
|
|
|
|
2021-09-30 17:15:45 +02:00
|
|
|
import './hljs-humanoid-light.css';
|
|
|
|
|
2022-02-01 13:25:23 +01:00
|
|
|
const PARAMETER_INTERNAL_MARGIN = 0.5;
|
|
|
|
|
2020-06-29 23:43:49 +02:00
|
|
|
const useStyles = makeStyles((theme) => ({
|
2022-01-28 15:17:25 +01:00
|
|
|
'@global': {
|
|
|
|
h1: {
|
|
|
|
fontSize: '1.6rem',
|
|
|
|
fontWeight: 500,
|
|
|
|
marginTop: theme.spacing(3),
|
|
|
|
marginBottom: theme.spacing(3)
|
|
|
|
},
|
|
|
|
h2: {
|
|
|
|
color: '#0B3C62',
|
|
|
|
fontSize: '1.2rem'
|
|
|
|
},
|
|
|
|
h3: {
|
|
|
|
fontSize: '1rem',
|
|
|
|
color: '#25699D'
|
|
|
|
},
|
|
|
|
hr: {
|
|
|
|
color: '#F9F9FB'
|
2022-02-01 13:25:23 +01:00
|
|
|
},
|
|
|
|
'.sidebarblock': {
|
|
|
|
'& .title': {
|
|
|
|
marginTop: theme.spacing(2),
|
|
|
|
color: '#25699D'
|
|
|
|
},
|
|
|
|
'& pre': {
|
|
|
|
marginLeft: '1rem',
|
|
|
|
marginTop: theme.spacing(PARAMETER_INTERNAL_MARGIN),
|
|
|
|
marginBottom: theme.spacing(PARAMETER_INTERNAL_MARGIN)
|
|
|
|
},
|
|
|
|
'& p': {
|
|
|
|
marginLeft: '1rem',
|
|
|
|
marginTop: theme.spacing(PARAMETER_INTERNAL_MARGIN),
|
|
|
|
marginBottom: theme.spacing(PARAMETER_INTERNAL_MARGIN)
|
|
|
|
}
|
2022-01-28 15:17:25 +01:00
|
|
|
}
|
|
|
|
},
|
2020-07-09 18:53:51 +02:00
|
|
|
ruleBar: {
|
|
|
|
borderBottom: '1px solid lightgrey',
|
|
|
|
},
|
|
|
|
ruleid: {
|
|
|
|
textAlign: 'center',
|
|
|
|
marginTop: theme.spacing(3),
|
|
|
|
marginBottom: theme.spacing(3),
|
2022-01-28 15:17:25 +01:00
|
|
|
color: 'black'
|
2020-07-09 18:53:51 +02:00
|
|
|
},
|
2021-09-30 12:20:26 +02:00
|
|
|
ruleidLink: {
|
|
|
|
color: 'inherit',
|
|
|
|
},
|
2020-07-09 18:53:51 +02:00
|
|
|
title: {
|
|
|
|
textAlign: 'justify',
|
|
|
|
marginTop: theme.spacing(4),
|
|
|
|
marginBottom: theme.spacing(4),
|
|
|
|
},
|
2022-01-31 19:44:44 +01:00
|
|
|
avoid: {
|
|
|
|
textDecoration: 'line-through'
|
|
|
|
},
|
2020-07-09 18:53:51 +02:00
|
|
|
coverage: {
|
|
|
|
marginBottom: theme.spacing(3),
|
|
|
|
},
|
2020-06-29 23:43:49 +02:00
|
|
|
description: {
|
2020-07-09 18:53:51 +02:00
|
|
|
textAlign: 'justify',
|
|
|
|
// marginBottom: theme.spacing(3),
|
2020-06-30 14:19:29 +02:00
|
|
|
},
|
2020-07-09 18:53:51 +02:00
|
|
|
|
|
|
|
// style used to center the tabs when there too few of them to fill the container
|
|
|
|
tabRoot: {
|
2022-01-28 15:17:25 +01:00
|
|
|
justifyContent: 'center'
|
2020-06-30 14:19:29 +02:00
|
|
|
},
|
2020-07-09 18:53:51 +02:00
|
|
|
tabScroller: {
|
2021-01-26 22:10:28 +01:00
|
|
|
flexGrow: 0
|
2021-05-20 11:24:59 +02:00
|
|
|
},
|
|
|
|
unimplemented: {
|
|
|
|
color: 'red'
|
2021-09-30 12:20:26 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
tab: {
|
|
|
|
display: 'flex',
|
|
|
|
|
2022-01-28 15:17:25 +01:00
|
|
|
'&::before': {
|
2021-09-30 12:20:26 +02:00
|
|
|
content: '""',
|
|
|
|
display: 'block',
|
|
|
|
width: theme.spacing(1),
|
|
|
|
height: theme.spacing(1),
|
|
|
|
marginRight: theme.spacing(1),
|
|
|
|
borderRadius: theme.spacing(1),
|
|
|
|
},
|
|
|
|
|
|
|
|
'& > .MuiTab-wrapper': {
|
|
|
|
width: 'auto',
|
|
|
|
}
|
|
|
|
},
|
2021-10-01 15:47:43 +02:00
|
|
|
coveredTab: {
|
2022-01-28 15:17:25 +01:00
|
|
|
'&::before': {
|
2021-10-01 15:47:43 +02:00
|
|
|
backgroundColor: RULE_STATE['covered'].color,
|
|
|
|
}
|
|
|
|
},
|
|
|
|
targetedTab: {
|
2022-01-28 15:17:25 +01:00
|
|
|
'&::before': {
|
2022-01-31 11:46:01 +01:00
|
|
|
borderColor: RULE_STATE['targeted'].color,
|
|
|
|
border: '1px solid',
|
|
|
|
backgroundColor: 'transparent'
|
2021-09-30 12:20:26 +02:00
|
|
|
}
|
|
|
|
},
|
2021-10-01 15:47:43 +02:00
|
|
|
removedTab: {
|
2022-01-28 15:17:25 +01:00
|
|
|
'&::before': {
|
2021-10-01 15:47:43 +02:00
|
|
|
backgroundColor: RULE_STATE['removed'].color,
|
2021-09-30 12:20:26 +02:00
|
|
|
}
|
|
|
|
},
|
2022-01-17 20:13:15 +01:00
|
|
|
closedTab: {
|
|
|
|
'&::before': {
|
|
|
|
backgroundColor: RULE_STATE['closed'].color,
|
|
|
|
}
|
|
|
|
},
|
|
|
|
deprecatedTab: {
|
|
|
|
'&::before': {
|
|
|
|
backgroundColor: RULE_STATE['deprecated'].color,
|
|
|
|
}
|
|
|
|
},
|
2020-06-29 23:43:49 +02:00
|
|
|
}));
|
|
|
|
|
2022-01-28 15:17:25 +01:00
|
|
|
const theme = createMuiTheme({});
|
|
|
|
|
2022-01-31 19:44:44 +01:00
|
|
|
type UsedStyles = ReturnType<typeof useStyles>;
|
|
|
|
|
2021-01-26 22:10:28 +01:00
|
|
|
const languageToJiraProject = new Map(Object.entries({
|
2022-01-17 20:13:15 +01:00
|
|
|
'PYTHON': 'SONARPY',
|
|
|
|
'ABAP': 'SONARABAP',
|
2023-05-04 11:55:34 +02:00
|
|
|
'AZURERESOURCEMANAGER': 'SONARIAC',
|
2022-01-17 20:13:15 +01:00
|
|
|
'CFAMILY': 'CPP',
|
2022-11-14 17:33:51 +01:00
|
|
|
'DOCKER': 'SONARIAC',
|
2022-01-17 20:13:15 +01:00
|
|
|
'JAVA': 'SONARJAVA',
|
|
|
|
'COBOL': 'SONARCOBOL',
|
|
|
|
'FLEX': 'SONARFLEX',
|
|
|
|
'HTML': 'SONARHTML',
|
|
|
|
'PHP': 'SONARPHP',
|
|
|
|
'PLI': 'SONARPLI',
|
|
|
|
'PLSQL': 'SONARPLSQL',
|
|
|
|
'RPG': 'SONARRPG',
|
|
|
|
'APEX': 'SONARSLANG',
|
|
|
|
'RUBY': 'SONARSLANG',
|
|
|
|
'KOTLIN': 'SONARKT',
|
|
|
|
'SCALA': 'SONARSLANG',
|
|
|
|
'GO': 'SONARSLANG',
|
|
|
|
'SECRETS': 'SECRETS',
|
|
|
|
'SWIFT': 'SONARSWIFT',
|
|
|
|
'TSQL': 'SONARTSQL',
|
|
|
|
'VB6': 'SONARVBSIX',
|
|
|
|
'XML': 'SONARXML',
|
|
|
|
'CLOUDFORMATION': 'SONARIAC',
|
|
|
|
'TERRAFORM': 'SONARIAC',
|
2022-05-25 10:04:20 +02:00
|
|
|
'KUBERNETES': 'SONARIAC',
|
2022-01-17 20:13:15 +01:00
|
|
|
'TEXT': 'SONARTEXT',
|
2021-01-26 22:10:28 +01:00
|
|
|
}));
|
2020-09-25 13:11:49 +02:00
|
|
|
|
2021-01-26 22:10:28 +01:00
|
|
|
const languageToGithubProject = new Map(Object.entries({
|
2022-01-17 20:13:15 +01:00
|
|
|
'ABAP': 'sonar-abap',
|
2023-05-04 11:55:34 +02:00
|
|
|
'AZURERESOURCEMANAGER': 'sonar-iac',
|
2022-01-17 20:13:15 +01:00
|
|
|
'CSHARP': 'sonar-dotnet',
|
2022-11-14 17:33:51 +01:00
|
|
|
'DOCKER': 'sonar-iac',
|
2022-01-17 20:13:15 +01:00
|
|
|
'VBNET': 'sonar-dotnet',
|
|
|
|
'JAVASCRIPT': 'SonarJS',
|
|
|
|
'TYPESCRIPT': 'SonarJS',
|
|
|
|
'SWIFT': 'sonar-swift',
|
|
|
|
'KOTLIN': 'sonar-kotlin',
|
|
|
|
'GO': 'slang-enterprise',
|
|
|
|
'SCALA': 'slang-enterprise',
|
|
|
|
'RUBY': 'slang-enterprise',
|
|
|
|
'APEX': 'slang-enterprise',
|
|
|
|
'HTML': 'sonar-html',
|
|
|
|
'COBOL': 'sonar-cobol',
|
|
|
|
'VB6': 'sonar-vb',
|
|
|
|
'JAVA': 'sonar-java',
|
|
|
|
'PLI': 'sonar-pli',
|
|
|
|
'CFAMILY': 'sonar-cpp',
|
|
|
|
'CSS': 'sonar-css',
|
|
|
|
'FLEX': 'sonar-flex',
|
|
|
|
'PHP': 'sonar-php',
|
|
|
|
'PLSQL': 'sonar-plsql',
|
|
|
|
'PYTHON': 'sonar-python',
|
|
|
|
'RPG': 'sonar-rpg',
|
|
|
|
'TSQL': 'sonar-tsql',
|
|
|
|
'XML': 'sonar-xml',
|
|
|
|
'CLOUDFORMATION': 'sonar-iac',
|
|
|
|
'TERRAFORM': 'sonar-iac',
|
2022-05-25 10:04:20 +02:00
|
|
|
'KUBERNETES': 'sonar-iac',
|
2022-01-17 20:13:15 +01:00
|
|
|
'SECRETS': 'sonar-secrets',
|
|
|
|
'TEXT': 'sonar-text',
|
2021-01-26 22:10:28 +01:00
|
|
|
}));
|
2020-09-25 13:11:49 +02:00
|
|
|
|
2021-06-10 15:30:10 +02:00
|
|
|
function ticketsAndImplementationPRsLinks(ruleNumber: string, title: string, language?: string) {
|
|
|
|
if (language) {
|
|
|
|
const upperCaseLanguage = language.toUpperCase();
|
|
|
|
const jiraProject = languageToJiraProject.get(upperCaseLanguage);
|
|
|
|
const githubProject = languageToGithubProject.get(upperCaseLanguage);
|
2022-01-14 17:07:50 +01:00
|
|
|
const titleWihoutQuotes = title.replace(/"/g, "'");
|
2021-06-10 15:30:10 +02:00
|
|
|
|
|
|
|
const implementationPRsLink = (
|
|
|
|
<Link href={`https://github.com/SonarSource/${githubProject}/pulls?q=is%3Apr+"S${ruleNumber}"+OR+"RSPEC-${ruleNumber}"`}>
|
|
|
|
Implementation Pull Requests
|
|
|
|
</Link>
|
|
|
|
);
|
|
|
|
|
|
|
|
if (jiraProject !== undefined) {
|
|
|
|
const ticketsLink = (
|
2021-09-23 16:14:10 +02:00
|
|
|
<Link href={`https://jira.sonarsource.com/issues/?jql=project%20%3D%20${jiraProject}%20AND%20(text%20~%20%22S${ruleNumber}%22%20OR%20text%20~%20%22RSPEC-${ruleNumber}%22%20OR%20text%20~%20"${titleWihoutQuotes}")`}>
|
2021-06-10 15:30:10 +02:00
|
|
|
Implementation tickets on Jira
|
|
|
|
</Link>
|
|
|
|
);
|
|
|
|
return {ticketsLink, implementationPRsLink};
|
|
|
|
} else {
|
|
|
|
const ticketsLink = (
|
|
|
|
<Link href={`https://github.com/SonarSource/${githubProject}/issues?q=is%3Aissue+"S${ruleNumber}"+OR+"RSPEC-${ruleNumber}"`}>
|
|
|
|
Implementation issues on GitHub
|
|
|
|
</Link>
|
|
|
|
);
|
|
|
|
return {ticketsLink, implementationPRsLink};
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const ticketsLink = (<div>Select a language to see the implementation tickets</div>);
|
|
|
|
const implementationPRsLink = (<div>Select a language to see the implementation pull requests</div>);
|
|
|
|
return {ticketsLink, implementationPRsLink};
|
|
|
|
}
|
|
|
|
}
|
2020-06-29 23:43:49 +02:00
|
|
|
|
2022-01-28 15:17:25 +01:00
|
|
|
function RuleThemeProvider({ children }: any) {
|
|
|
|
useStyles();
|
|
|
|
return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
|
|
|
|
}
|
|
|
|
|
2022-01-31 19:44:44 +01:00
|
|
|
interface PageMetadata {
|
|
|
|
title: string;
|
|
|
|
languagesTabs: JSX.Element[] | null;
|
|
|
|
avoid: boolean;
|
|
|
|
prUrl: string | undefined;
|
|
|
|
branch: string;
|
|
|
|
coverage: any;
|
|
|
|
jsonString: string | undefined;
|
|
|
|
}
|
2020-06-29 23:43:49 +02:00
|
|
|
|
2022-01-31 19:44:44 +01:00
|
|
|
function usePageMetadata(ruleid: string, language: string, classes: UsedStyles): PageMetadata {
|
2022-01-17 20:13:15 +01:00
|
|
|
const metadataUrl = `${process.env.PUBLIC_URL}/rules/${ruleid}/${language ?? 'default'}-metadata.json`;
|
2021-01-26 22:10:28 +01:00
|
|
|
let [metadataJSON, metadataError, metadataIsLoading] = useFetch<RuleMetadata>(metadataUrl);
|
2020-06-29 23:43:49 +02:00
|
|
|
|
2022-01-17 20:13:15 +01:00
|
|
|
let coverage: any = 'Loading...';
|
|
|
|
let title = 'Loading...';
|
2022-01-31 19:44:44 +01:00
|
|
|
let avoid = false;
|
2020-07-06 11:03:32 +02:00
|
|
|
let metadataJSONString;
|
2020-06-30 14:19:29 +02:00
|
|
|
let languagesTabs = null;
|
2021-05-20 11:24:59 +02:00
|
|
|
let prUrl: string | undefined = undefined;
|
2022-01-31 19:44:44 +01:00
|
|
|
let branch = 'master';
|
|
|
|
const { ruleCoverage, allLangsRuleCoverage, ruleStateInAnalyzer } = useRuleCoverage();
|
2021-01-26 22:10:28 +01:00
|
|
|
if (metadataJSON && !metadataIsLoading && !metadataError) {
|
2021-05-20 11:24:59 +02:00
|
|
|
title = metadataJSON.title;
|
|
|
|
if ('prUrl' in metadataJSON) {
|
|
|
|
prUrl = metadataJSON.prUrl;
|
|
|
|
}
|
2021-06-03 16:30:50 +02:00
|
|
|
branch = metadataJSON.branch;
|
2023-05-19 08:57:30 +02:00
|
|
|
metadataJSON.languagesSupport.sort((a, b) => a.name.localeCompare(b.name));
|
2022-01-31 19:44:44 +01:00
|
|
|
const ruleStates = metadataJSON.languagesSupport.map(({ name, status }) => ({
|
|
|
|
name,
|
|
|
|
ruleState: ruleStateInAnalyzer(name, metadataJSON!.allKeys, status)
|
|
|
|
}));
|
|
|
|
languagesTabs = ruleStates.map(({ name, ruleState }) => {
|
2021-10-01 15:47:43 +02:00
|
|
|
const classNames = classes.tab + ' ' + (classes as any)[ruleState + 'Tab'];
|
2022-01-17 20:13:15 +01:00
|
|
|
return <Tab key={name} label={name} value={name} className={classNames} />;
|
2021-09-30 12:20:26 +02:00
|
|
|
});
|
2022-01-31 19:44:44 +01:00
|
|
|
avoid = !ruleStates.some(({ ruleState }) => ruleState === 'covered' || ruleState === 'targeted');
|
2020-07-06 11:03:32 +02:00
|
|
|
metadataJSONString = JSON.stringify(metadataJSON, null, 2);
|
2020-07-09 18:53:51 +02:00
|
|
|
|
2021-09-30 10:59:30 +02:00
|
|
|
const coverageMapper = (key: any, range: any) => {
|
2022-01-17 20:13:15 +01:00
|
|
|
if (typeof range === 'string') {
|
2021-09-30 10:59:30 +02:00
|
|
|
return (
|
2022-01-14 17:07:50 +01:00
|
|
|
<li key={key} >{key}: {range}</li>
|
2021-09-30 10:59:30 +02:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
<li>Not covered for {key} anymore. Was covered from {range['since']} to {range['until']}.</li>
|
|
|
|
);
|
|
|
|
}
|
2021-06-10 15:30:10 +02:00
|
|
|
};
|
|
|
|
if (language) {
|
|
|
|
coverage = ruleCoverage(language, metadataJSON.allKeys, coverageMapper);
|
|
|
|
} else {
|
|
|
|
coverage = allLangsRuleCoverage(metadataJSON.allKeys, coverageMapper);
|
|
|
|
}
|
2020-06-29 23:43:49 +02:00
|
|
|
}
|
|
|
|
|
2022-01-17 20:13:15 +01:00
|
|
|
if (coverage !== 'Not Covered') {
|
2021-09-22 13:49:19 +02:00
|
|
|
prUrl = undefined;
|
|
|
|
branch = 'master';
|
|
|
|
}
|
|
|
|
|
2022-01-31 19:44:44 +01:00
|
|
|
return {
|
|
|
|
title,
|
|
|
|
languagesTabs,
|
|
|
|
avoid,
|
|
|
|
prUrl,
|
|
|
|
branch,
|
|
|
|
coverage,
|
|
|
|
jsonString: metadataJSONString
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-02-07 19:16:41 +01:00
|
|
|
function getRspecPath(rspecId: string, language?: string) {
|
|
|
|
return '/rspec#/rspec/' + rspecId;
|
|
|
|
}
|
|
|
|
|
|
|
|
function useDescription(metadata: PageMetadata, ruleid: string, language?: string) {
|
2023-05-19 08:57:30 +02:00
|
|
|
const editOnGithubUrl =
|
|
|
|
`https://github.com/SonarSource/rspec/blob/${metadata.branch}/rules/${ruleid}${language ? '/' + language : ''}`;
|
2022-01-31 19:44:44 +01:00
|
|
|
|
2022-02-07 19:16:41 +01:00
|
|
|
function htmlReplacement(domNode: Element) {
|
|
|
|
if (domNode.name === 'a' && domNode.attribs && domNode.attribs['data-rspec-id']) {
|
|
|
|
const props = attributesToProps(domNode.attribs);
|
|
|
|
return <a href={getRspecPath(domNode.attribs['data-rspec-id'], language)} {...props}>
|
|
|
|
{domToReact(domNode.children)}
|
|
|
|
</a>;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (domNode.name === 'code' && domNode.attribs && domNode.attribs['data-lang']) {
|
|
|
|
return <Highlight className={domNode.attribs['data-lang']}>
|
|
|
|
{domToReact(domNode.children)}
|
|
|
|
</Highlight>;
|
|
|
|
}
|
|
|
|
|
|
|
|
return undefined; // No modification.
|
|
|
|
}
|
2022-01-31 19:44:44 +01:00
|
|
|
|
|
|
|
const descUrl = `${process.env.PUBLIC_URL}/rules/${ruleid}/${language ?? 'default'}-description.html`;
|
|
|
|
|
|
|
|
const [descHTML, descError, descIsLoading] = useFetch<string>(descUrl, false);
|
2021-06-03 16:30:50 +02:00
|
|
|
|
2021-01-26 22:10:28 +01:00
|
|
|
if (descHTML !== null && !descIsLoading && !descError) {
|
2022-01-31 19:44:44 +01:00
|
|
|
return <div>
|
2022-02-07 19:16:41 +01:00
|
|
|
{parse(descHTML, { replace: (d: DOMNode) => htmlReplacement(d as Element) })}
|
2020-07-06 11:03:32 +02:00
|
|
|
<hr />
|
2022-01-31 19:44:44 +01:00
|
|
|
<a href={editOnGithubUrl}>Edit on Github</a><br />
|
2020-07-06 11:03:32 +02:00
|
|
|
<hr />
|
2022-01-31 19:44:44 +01:00
|
|
|
<Highlight className='json'>{metadata.jsonString}</Highlight>
|
2020-07-06 11:03:32 +02:00
|
|
|
</div>;
|
2020-06-30 09:22:25 +02:00
|
|
|
}
|
2022-01-31 19:44:44 +01:00
|
|
|
return <div>Loading...</div>;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function RulePage(props: any) {
|
|
|
|
// language can be absent
|
|
|
|
const {ruleid, language} = props.match.params;
|
|
|
|
document.title = ruleid;
|
|
|
|
|
|
|
|
const history = useHistory();
|
|
|
|
function handleLanguageChange(event: any, lang: string) {
|
|
|
|
history.push(`/${ruleid}/${lang}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
const classes = useStyles();
|
|
|
|
|
|
|
|
const metadata = usePageMetadata(ruleid, language, classes);
|
|
|
|
const description = useDescription(metadata, ruleid, language);
|
|
|
|
|
2021-05-20 11:24:59 +02:00
|
|
|
let prLink = <></>;
|
2022-01-31 19:44:44 +01:00
|
|
|
if (metadata.prUrl) {
|
|
|
|
prLink = <div>
|
|
|
|
<span className={classes.unimplemented}>Not implemented (see <a href={metadata.prUrl}>PR</a>)</span>
|
|
|
|
</div>;
|
2021-05-20 11:24:59 +02:00
|
|
|
}
|
2021-06-10 15:30:10 +02:00
|
|
|
const ruleNumber = ruleid.substring(1);
|
2021-05-20 10:31:47 +02:00
|
|
|
|
|
|
|
const specificationPRsLink = (
|
|
|
|
<Link href={`https://github.com/SonarSource/rspec/pulls?q=is%3Apr+"S${ruleNumber}"+OR+"RSPEC-${ruleNumber}"`}>
|
|
|
|
Specification Pull Requests
|
|
|
|
</Link>
|
|
|
|
);
|
|
|
|
|
2022-01-31 19:44:44 +01:00
|
|
|
const {ticketsLink, implementationPRsLink} = ticketsAndImplementationPRsLinks(ruleNumber, metadata.title, language);
|
2021-06-10 15:30:10 +02:00
|
|
|
const tabsValue = language ? {'value' : language} : {'value': false};
|
2020-07-09 18:53:51 +02:00
|
|
|
|
2020-06-29 23:43:49 +02:00
|
|
|
return (
|
2020-06-30 14:19:29 +02:00
|
|
|
<div>
|
2022-01-28 15:17:25 +01:00
|
|
|
<div className={classes.ruleBar}>
|
|
|
|
<Container>
|
|
|
|
<Typography variant="h2" classes={{ root: classes.ruleid }}>
|
2022-01-31 19:44:44 +01:00
|
|
|
<Link className={`${classes.ruleidLink} ${metadata.avoid ? classes.avoid : ''}`}
|
|
|
|
component={RouterLink} to={`/${ruleid}`} underline="none">{ruleid}</Link>
|
2022-01-28 15:17:25 +01:00
|
|
|
</Typography>
|
|
|
|
<Typography variant="h4" classes={{ root: classes.ruleid }}>{prLink}</Typography>
|
|
|
|
<Tabs
|
2021-09-30 12:20:26 +02:00
|
|
|
{...tabsValue}
|
|
|
|
onChange={handleLanguageChange}
|
|
|
|
indicatorColor="primary"
|
|
|
|
textColor="primary"
|
|
|
|
variant="scrollable"
|
|
|
|
scrollButtons="auto"
|
|
|
|
classes={{ root: classes.tabRoot, scroller: classes.tabScroller }}
|
2022-01-28 15:17:25 +01:00
|
|
|
>
|
2022-01-31 19:44:44 +01:00
|
|
|
{metadata.languagesTabs}
|
2022-01-28 15:17:25 +01:00
|
|
|
</Tabs>
|
|
|
|
</Container>
|
|
|
|
</div>
|
2021-05-20 10:31:47 +02:00
|
|
|
|
2022-01-28 15:17:25 +01:00
|
|
|
<RuleThemeProvider>
|
|
|
|
<Container maxWidth="md">
|
2022-01-31 19:44:44 +01:00
|
|
|
<h1>{metadata.title}</h1>
|
2022-01-28 15:17:25 +01:00
|
|
|
<hr />
|
|
|
|
<Box className={classes.coverage}>
|
|
|
|
<h2>Covered Since</h2>
|
|
|
|
<ul>
|
2022-01-31 19:44:44 +01:00
|
|
|
{metadata.coverage}
|
2022-01-28 15:17:25 +01:00
|
|
|
</ul>
|
|
|
|
</Box>
|
2020-09-25 13:11:49 +02:00
|
|
|
|
2022-01-28 15:17:25 +01:00
|
|
|
<Box className={classes.coverage}>
|
|
|
|
<h2>Related Tickets and Pull Requests</h2>
|
|
|
|
<ul>
|
|
|
|
{specificationPRsLink}
|
|
|
|
</ul>
|
|
|
|
<ul>
|
|
|
|
{implementationPRsLink}
|
|
|
|
</ul>
|
|
|
|
<ul>
|
|
|
|
{ticketsLink}
|
|
|
|
</ul>
|
|
|
|
</Box>
|
2021-05-20 10:31:47 +02:00
|
|
|
|
2022-01-28 15:17:25 +01:00
|
|
|
<Box>
|
|
|
|
<Typography component={'span'} className={classes.description}>
|
|
|
|
{description}
|
|
|
|
</Typography>
|
|
|
|
</Box>
|
|
|
|
</Container>
|
|
|
|
</RuleThemeProvider>
|
2020-06-30 14:19:29 +02:00
|
|
|
</div>
|
2020-06-29 23:43:49 +02:00
|
|
|
);
|
2020-07-06 11:03:32 +02:00
|
|
|
}
|