RULEAPI-705 differentiate closed and obsolete rules in the UI (#704)
This commit is contained in:
parent
26267dcbf0
commit
c8c825feb0
@ -7,3 +7,4 @@ sonar.tests=src
|
||||
sonar.test.inclusions=**/*.test.js,**/*.test.jsx,**/*.test.ts,**/*.test.tsx
|
||||
sonar.javascript.coveragePlugin=lcov
|
||||
sonar.javascript.lcov.reportPaths=coverage/lcov.info
|
||||
sonar.cpd.exclusions=src/deployment/__tests__/resources/**
|
||||
|
@ -83,65 +83,75 @@ const useStyles = makeStyles((theme) => ({
|
||||
backgroundColor: RULE_STATE['removed'].color,
|
||||
}
|
||||
},
|
||||
closedTab: {
|
||||
'&::before': {
|
||||
backgroundColor: RULE_STATE['closed'].color,
|
||||
}
|
||||
},
|
||||
deprecatedTab: {
|
||||
'&::before': {
|
||||
backgroundColor: RULE_STATE['deprecated'].color,
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
const languageToJiraProject = new Map(Object.entries({
|
||||
"PYTHON": "SONARPY",
|
||||
"ABAP": "SONARABAP",
|
||||
"CFAMILY": "CPP",
|
||||
"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",
|
||||
"TEXT": "SONARTEXT",
|
||||
'PYTHON': 'SONARPY',
|
||||
'ABAP': 'SONARABAP',
|
||||
'CFAMILY': 'CPP',
|
||||
'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',
|
||||
'TEXT': 'SONARTEXT',
|
||||
}));
|
||||
|
||||
const languageToGithubProject = new Map(Object.entries({
|
||||
"ABAP": "sonar-abap",
|
||||
"CSHARP": "sonar-dotnet",
|
||||
"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",
|
||||
"SECRETS": "sonar-secrets",
|
||||
"TEXT": "sonar-text",
|
||||
'ABAP': 'sonar-abap',
|
||||
'CSHARP': 'sonar-dotnet',
|
||||
'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',
|
||||
'SECRETS': 'sonar-secrets',
|
||||
'TEXT': 'sonar-text',
|
||||
}));
|
||||
|
||||
function ticketsAndImplementationPRsLinks(ruleNumber: string, title: string, language?: string) {
|
||||
@ -193,16 +203,16 @@ export function RulePage(props: any) {
|
||||
const classes = useStyles();
|
||||
let branch = 'master'
|
||||
|
||||
let descUrl = process.env.PUBLIC_URL + '/rules/' + ruleid + "/" + (language ?? "default") + "-description.html";
|
||||
let metadataUrl = process.env.PUBLIC_URL + '/rules/' + ruleid + "/" + (language ?? "default") + "-metadata.json";
|
||||
const descUrl = `${process.env.PUBLIC_URL}/rules/${ruleid}/${language ?? 'default'}-description.html`;
|
||||
const metadataUrl = `${process.env.PUBLIC_URL}/rules/${ruleid}/${language ?? 'default'}-metadata.json`;
|
||||
|
||||
let [descHTML, descError, descIsLoading] = useFetch<string>(descUrl, false);
|
||||
let [metadataJSON, metadataError, metadataIsLoading] = useFetch<RuleMetadata>(metadataUrl);
|
||||
|
||||
const {ruleCoverage, allLangsRuleCoverage, ruleStateInAnalyzer} = useRuleCoverage();
|
||||
let coverage: any = "Loading...";
|
||||
let coverage: any = 'Loading...';
|
||||
|
||||
let title = "Loading..."
|
||||
let title = 'Loading...';
|
||||
let metadataJSONString;
|
||||
let languagesTabs = null;
|
||||
let prUrl: string | undefined = undefined;
|
||||
@ -212,16 +222,16 @@ export function RulePage(props: any) {
|
||||
prUrl = metadataJSON.prUrl;
|
||||
}
|
||||
branch = metadataJSON.branch;
|
||||
metadataJSON.all_languages.sort();
|
||||
languagesTabs = metadataJSON.all_languages.map(lang => {
|
||||
const ruleState = ruleStateInAnalyzer(lang, metadataJSON!.allKeys);
|
||||
metadataJSON.languagesSupport.sort();
|
||||
languagesTabs = metadataJSON.languagesSupport.map(({ name, status }) => {
|
||||
const ruleState = ruleStateInAnalyzer(name, metadataJSON!.allKeys, status);
|
||||
const classNames = classes.tab + ' ' + (classes as any)[ruleState + 'Tab'];
|
||||
return <Tab key={lang} label={lang} value={lang} className={classNames} />;
|
||||
return <Tab key={name} label={name} value={name} className={classNames} />;
|
||||
});
|
||||
metadataJSONString = JSON.stringify(metadataJSON, null, 2);
|
||||
|
||||
const coverageMapper = (key: any, range: any) => {
|
||||
if (typeof range === "string") {
|
||||
if (typeof range === 'string') {
|
||||
return (
|
||||
<li key={key} >{key}: {range}</li>
|
||||
);
|
||||
@ -238,7 +248,7 @@ export function RulePage(props: any) {
|
||||
}
|
||||
}
|
||||
|
||||
if (coverage !== "Not Covered") {
|
||||
if (coverage !== 'Not Covered') {
|
||||
prUrl = undefined;
|
||||
branch = 'master';
|
||||
}
|
||||
|
@ -48,6 +48,22 @@ const useStyles = makeStyles((theme) => ({
|
||||
backgroundColor: RULE_STATE['removed'].darker
|
||||
},
|
||||
},
|
||||
deprecatedLanguageChip: {
|
||||
marginRight: theme.spacing(1),
|
||||
marginTop: theme.spacing(2),
|
||||
backgroundColor: RULE_STATE['deprecated'].color,
|
||||
'&:hover, &:focus': {
|
||||
backgroundColor: RULE_STATE['deprecated'].darker
|
||||
},
|
||||
},
|
||||
closedLanguageChip: {
|
||||
marginRight: theme.spacing(1),
|
||||
marginTop: theme.spacing(2),
|
||||
backgroundColor: RULE_STATE['closed'].color,
|
||||
'&:hover, &:focus': {
|
||||
backgroundColor: RULE_STATE['closed'].darker
|
||||
},
|
||||
},
|
||||
targetedMarker: {
|
||||
marginTop: theme.spacing(2),
|
||||
marginRight: theme.spacing(2),
|
||||
@ -65,6 +81,18 @@ const useStyles = makeStyles((theme) => ({
|
||||
marginRight: theme.spacing(2),
|
||||
borderColor: RULE_STATE['removed'].color,
|
||||
color: RULE_STATE['removed'].color
|
||||
},
|
||||
deprecatedMarker: {
|
||||
marginTop: theme.spacing(2),
|
||||
marginRight: theme.spacing(2),
|
||||
borderColor: RULE_STATE['deprecated'].color,
|
||||
color: RULE_STATE['deprecated'].color
|
||||
},
|
||||
closedMarker: {
|
||||
marginTop: theme.spacing(2),
|
||||
marginRight: theme.spacing(2),
|
||||
borderColor: RULE_STATE['closed'].color,
|
||||
color: RULE_STATE['closed'].color
|
||||
}
|
||||
}));
|
||||
|
||||
@ -79,26 +107,39 @@ export function SearchHit(props: SearchHitProps) {
|
||||
const coveredLanguages: JSX.Element[] = [];
|
||||
const targetedLanguages: JSX.Element[] = [];
|
||||
const removedLanguages: JSX.Element[] = [];
|
||||
const deprecatedLanguages: JSX.Element[] = [];
|
||||
const closedLanguages: JSX.Element[] = [];
|
||||
|
||||
const actualLanguages = props.data.languages.filter(language => language !== 'default');
|
||||
const actualLanguages = props.data.languages.filter(l => l.name !== 'default');
|
||||
actualLanguages.forEach(lang => {
|
||||
const ruleState = ruleStateInAnalyzer(lang, props.data.all_keys);
|
||||
const chip = <Link key={lang} component={RouterLink} to={`/${props.data.id}/${lang}`}
|
||||
const ruleState = ruleStateInAnalyzer(lang.name, props.data.all_keys, lang.status);
|
||||
const chip = <Link key={lang.name} component={RouterLink} to={`/${props.data.id}/${lang.name}`}
|
||||
style={{ textDecoration: 'none' }}>
|
||||
<Chip
|
||||
classes={{root: (classes as any)[ruleState + 'LanguageChip']}}
|
||||
label={lang}
|
||||
label={lang.name}
|
||||
color="primary"
|
||||
clickable
|
||||
key="{lang}"
|
||||
/>
|
||||
</Link>;
|
||||
if (ruleState === 'covered') {
|
||||
coveredLanguages.push(chip);
|
||||
} else if (ruleState === 'targeted') {
|
||||
switch(ruleState) {
|
||||
case 'targeted':
|
||||
targetedLanguages.push(chip);
|
||||
} else {
|
||||
break;
|
||||
case 'removed':
|
||||
removedLanguages.push(chip);
|
||||
break;
|
||||
case 'deprecated':
|
||||
deprecatedLanguages.push(chip);
|
||||
break;
|
||||
case 'closed':
|
||||
closedLanguages.push(chip);
|
||||
break;
|
||||
case 'covered':
|
||||
default:
|
||||
coveredLanguages.push(chip);
|
||||
break;
|
||||
}
|
||||
});
|
||||
const titles = props.data.titles.map(title => (
|
||||
@ -125,6 +166,17 @@ export function SearchHit(props: SearchHitProps) {
|
||||
{removedLanguages}
|
||||
</Typography>;
|
||||
|
||||
const deprecatedBlock = deprecatedLanguages.length === 0 ? <></>
|
||||
: <Typography key="closed-marker" variant="body2" component="div" classes={{ root: classes.language }}>
|
||||
<Chip classes={{ root: classes.deprecatedMarker }} label="Deprecated" color="secondary" variant="outlined" />
|
||||
{deprecatedLanguages}
|
||||
</Typography>;
|
||||
const closedBlock = closedLanguages.length === 0 ? <></>
|
||||
: <Typography key="deprecated-marker" variant="body2" component="div" classes={{ root: classes.language }}>
|
||||
<Chip classes={{ root: classes.closedMarker }} label="Closed" color="secondary" variant="outlined" />
|
||||
{closedLanguages}
|
||||
</Typography>;
|
||||
|
||||
return (
|
||||
<Card variant="outlined" classes={{root: classes.searchHit}}>
|
||||
<CardContent>
|
||||
@ -137,6 +189,8 @@ export function SearchHit(props: SearchHitProps) {
|
||||
{coveredBlock}
|
||||
{targetedBlock}
|
||||
{removedBlock}
|
||||
{deprecatedBlock}
|
||||
{closedBlock}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
|
@ -7,7 +7,7 @@ import { Router } from 'react-router-dom';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { fetchMock } from '../testutils'
|
||||
|
||||
const rulesPath = path.join(__dirname, '..', 'deployment', '__tests__', 'resources', 'plugin_rules');
|
||||
const rulesPath = path.join(__dirname, '..', 'deployment', '__tests__', 'resources', 'metadata');
|
||||
|
||||
function readRuleFile(ruleId, filename) {
|
||||
return fs.readFileSync(path.join(rulesPath, ruleId, filename)).toString();
|
||||
|
@ -17,7 +17,7 @@ function normalize(obj) {
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
const rulePath = path.join(__dirname, '..', 'deployment', '__tests__', 'resources', 'plugin_rules');
|
||||
const rulePath = path.join(__dirname, '..', 'deployment', '__tests__', 'resources', 'metadata');
|
||||
const [indexStore, indexAggregates] = buildIndexStore(rulePath);
|
||||
const searchIndex = buildSearchIndex(indexStore);
|
||||
const rootUrl = process.env.PUBLIC_URL;
|
||||
@ -26,8 +26,9 @@ beforeEach(() => {
|
||||
mockUrls[`${rootUrl}/rules/rule-index-store.json`] = {json: normalize(indexStore)};
|
||||
mockUrls[`${rootUrl}/rules/rule-index-aggregates.json`] = {json: normalize(indexAggregates)};
|
||||
mockUrls[`${rootUrl}/covered_rules.json`] = {json:
|
||||
{'ABAP': {'S100': 'ver1', 'S200': 'ver2'},
|
||||
'C': {'S100': 'c1', 'S234': {'since': 'c2', 'until': 'c3'}}}
|
||||
{'CPP': {'S1000': 'ver1', 'S987': 'ver2', 'S3457': 'ver1'},
|
||||
'C': {'S1000': 'c1', 'S234': {'since': 'c2', 'until': 'c3'}},
|
||||
'PY': {'S3457': {'since': 'p2', 'until': 'p3'}}}
|
||||
};
|
||||
jest.spyOn(global, 'fetch').mockImplementation(fetchMock(mockUrls));
|
||||
});
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -341,12 +341,12 @@ exports[`renders the list of all rules 1`] = `
|
||||
class="MuiTypography-root makeStyles-language-16 MuiTypography-body2"
|
||||
>
|
||||
<div
|
||||
class="MuiChip-root makeStyles-targetedMarker-20 MuiChip-colorSecondary MuiChip-outlined MuiChip-outlinedSecondary"
|
||||
class="MuiChip-root makeStyles-coveredMarker-23 MuiChip-colorPrimary MuiChip-outlined MuiChip-outlinedPrimary"
|
||||
>
|
||||
<span
|
||||
class="MuiChip-label"
|
||||
>
|
||||
Targeted
|
||||
Covered
|
||||
</span>
|
||||
</div>
|
||||
<a
|
||||
@ -355,7 +355,7 @@ exports[`renders the list of all rules 1`] = `
|
||||
style="text-decoration: none;"
|
||||
>
|
||||
<div
|
||||
class="MuiButtonBase-root MuiChip-root makeStyles-targetedLanguageChip-18 MuiChip-colorPrimary MuiChip-clickableColorPrimary MuiChip-clickable"
|
||||
class="MuiButtonBase-root MuiChip-root makeStyles-coveredLanguageChip-17 MuiChip-colorPrimary MuiChip-clickableColorPrimary MuiChip-clickable"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
@ -374,7 +374,7 @@ exports[`renders the list of all rules 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiBox-root MuiBox-root-23 makeStyles-searchHitBox-2"
|
||||
class="MuiBox-root MuiBox-root-27 makeStyles-searchHitBox-2"
|
||||
>
|
||||
<div
|
||||
class="MuiPaper-root MuiCard-root makeStyles-searchHit-14 MuiPaper-outlined MuiPaper-rounded"
|
||||
@ -413,12 +413,12 @@ exports[`renders the list of all rules 1`] = `
|
||||
class="MuiTypography-root makeStyles-language-16 MuiTypography-body2"
|
||||
>
|
||||
<div
|
||||
class="MuiChip-root makeStyles-targetedMarker-20 MuiChip-colorSecondary MuiChip-outlined MuiChip-outlinedSecondary"
|
||||
class="MuiChip-root makeStyles-coveredMarker-23 MuiChip-colorPrimary MuiChip-outlined MuiChip-outlinedPrimary"
|
||||
>
|
||||
<span
|
||||
class="MuiChip-label"
|
||||
>
|
||||
Targeted
|
||||
Covered
|
||||
</span>
|
||||
</div>
|
||||
<a
|
||||
@ -427,7 +427,7 @@ exports[`renders the list of all rules 1`] = `
|
||||
style="text-decoration: none;"
|
||||
>
|
||||
<div
|
||||
class="MuiButtonBase-root MuiChip-root makeStyles-targetedLanguageChip-18 MuiChip-colorPrimary MuiChip-clickableColorPrimary MuiChip-clickable"
|
||||
class="MuiButtonBase-root MuiChip-root makeStyles-coveredLanguageChip-17 MuiChip-colorPrimary MuiChip-clickableColorPrimary MuiChip-clickable"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
@ -441,6 +441,19 @@ exports[`renders the list of all rules 1`] = `
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="MuiTypography-root makeStyles-language-16 MuiTypography-body2"
|
||||
>
|
||||
<div
|
||||
class="MuiChip-root makeStyles-targetedMarker-22 MuiChip-colorSecondary MuiChip-outlined MuiChip-outlinedSecondary"
|
||||
>
|
||||
<span
|
||||
class="MuiChip-label"
|
||||
>
|
||||
Targeted
|
||||
</span>
|
||||
</div>
|
||||
<a
|
||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorPrimary"
|
||||
href="/S3457/csharp"
|
||||
@ -461,33 +474,26 @@ exports[`renders the list of all rules 1`] = `
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorPrimary"
|
||||
href="/S3457/java"
|
||||
style="text-decoration: none;"
|
||||
</div>
|
||||
<div
|
||||
class="MuiTypography-root makeStyles-language-16 MuiTypography-body2"
|
||||
>
|
||||
<div
|
||||
class="MuiButtonBase-root MuiChip-root makeStyles-targetedLanguageChip-18 MuiChip-colorPrimary MuiChip-clickableColorPrimary MuiChip-clickable"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
class="MuiChip-root makeStyles-removedMarker-24 MuiChip-colorSecondary MuiChip-outlined MuiChip-outlinedSecondary"
|
||||
>
|
||||
<span
|
||||
class="MuiChip-label"
|
||||
>
|
||||
java
|
||||
Removed
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorPrimary"
|
||||
href="/S3457/python"
|
||||
style="text-decoration: none;"
|
||||
>
|
||||
<div
|
||||
class="MuiButtonBase-root MuiChip-root makeStyles-targetedLanguageChip-18 MuiChip-colorPrimary MuiChip-clickableColorPrimary MuiChip-clickable"
|
||||
class="MuiButtonBase-root MuiChip-root makeStyles-removedLanguageChip-19 MuiChip-colorPrimary MuiChip-clickableColorPrimary MuiChip-clickable"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
@ -502,11 +508,44 @@ exports[`renders the list of all rules 1`] = `
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="MuiTypography-root makeStyles-language-16 MuiTypography-body2"
|
||||
>
|
||||
<div
|
||||
class="MuiChip-root makeStyles-closedMarker-26 MuiChip-colorSecondary MuiChip-outlined MuiChip-outlinedSecondary"
|
||||
>
|
||||
<span
|
||||
class="MuiChip-label"
|
||||
>
|
||||
Closed
|
||||
</span>
|
||||
</div>
|
||||
<a
|
||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorPrimary"
|
||||
href="/S3457/java"
|
||||
style="text-decoration: none;"
|
||||
>
|
||||
<div
|
||||
class="MuiButtonBase-root MuiChip-root makeStyles-closedLanguageChip-21 MuiChip-colorPrimary MuiChip-clickableColorPrimary MuiChip-clickable"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="MuiChip-label"
|
||||
>
|
||||
java
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiBox-root MuiBox-root-24 makeStyles-searchHitBox-2"
|
||||
class="MuiBox-root MuiBox-root-28 makeStyles-searchHitBox-2"
|
||||
>
|
||||
<div
|
||||
class="MuiPaper-root MuiCard-root makeStyles-searchHit-14 MuiPaper-outlined MuiPaper-rounded"
|
||||
@ -535,12 +574,12 @@ exports[`renders the list of all rules 1`] = `
|
||||
class="MuiTypography-root makeStyles-language-16 MuiTypography-body2"
|
||||
>
|
||||
<div
|
||||
class="MuiChip-root makeStyles-targetedMarker-20 MuiChip-colorSecondary MuiChip-outlined MuiChip-outlinedSecondary"
|
||||
class="MuiChip-root makeStyles-coveredMarker-23 MuiChip-colorPrimary MuiChip-outlined MuiChip-outlinedPrimary"
|
||||
>
|
||||
<span
|
||||
class="MuiChip-label"
|
||||
>
|
||||
Targeted
|
||||
Covered
|
||||
</span>
|
||||
</div>
|
||||
<a
|
||||
@ -549,7 +588,7 @@ exports[`renders the list of all rules 1`] = `
|
||||
style="text-decoration: none;"
|
||||
>
|
||||
<div
|
||||
class="MuiButtonBase-root MuiChip-root makeStyles-targetedLanguageChip-18 MuiChip-colorPrimary MuiChip-clickableColorPrimary MuiChip-clickable"
|
||||
class="MuiButtonBase-root MuiChip-root makeStyles-coveredLanguageChip-17 MuiChip-colorPrimary MuiChip-clickableColorPrimary MuiChip-clickable"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { generate_rules_description } from '../description';
|
||||
import { generateRulesDescription } from '../description';
|
||||
import { withTestDir, createFiles } from '../testutils';
|
||||
|
||||
describe('description generation', () => {
|
||||
@ -14,7 +14,7 @@ test('generates html from asciidoc', () => {
|
||||
'Specific content'].join('\n')
|
||||
});
|
||||
return withTestDir(async (dstPath) => {
|
||||
generate_rules_description(srcPath, dstPath);
|
||||
generateRulesDescription(srcPath, dstPath);
|
||||
|
||||
const ruleHtml = fs.readFileSync(path.join(dstPath, 'S100', 'java-description.html'));
|
||||
expect(ruleHtml.toString()).toEqual(
|
||||
|
@ -1,7 +1,7 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { generate_one_rule_metadata, generate_rules_metadata } from '../metadata';
|
||||
import { generateOneRuleMetadata, generateRulesMetadata } from '../metadata';
|
||||
import { withTestDir, createFiles } from '../testutils';
|
||||
|
||||
describe('metadata generation', () => {
|
||||
@ -21,7 +21,7 @@ describe('metadata generation', () => {
|
||||
}),
|
||||
});
|
||||
return withTestDir(async (dstPath) => {
|
||||
generate_rules_metadata(srcPath, dstPath);
|
||||
generateRulesMetadata(srcPath, dstPath);
|
||||
const javaStrMetadata = fs.readFileSync(`${dstPath}/S100/java-metadata.json`);
|
||||
const javaMetadata = JSON.parse(javaStrMetadata.toString());
|
||||
expect(javaMetadata).toMatchObject({
|
||||
@ -39,6 +39,39 @@ describe('metadata generation', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('check status computation', () => {
|
||||
return withTestDir((srcPath) => {
|
||||
createFiles(srcPath, {
|
||||
'S100/metadata.json': JSON.stringify({
|
||||
title: 'Rule S100',
|
||||
status: 'ready'
|
||||
}),
|
||||
'S100/java/metadata.json': JSON.stringify({
|
||||
title: 'Java Rule S100'
|
||||
}),
|
||||
'S100/python/metadata.json': JSON.stringify({
|
||||
status: 'closed'
|
||||
}),
|
||||
});
|
||||
return withTestDir(async (dstPath) => {
|
||||
generateRulesMetadata(srcPath, dstPath);
|
||||
const javaStrMetadata = fs.readFileSync(`${dstPath}/S100/java-metadata.json`);
|
||||
const pythonStrMetadata = fs.readFileSync(`${dstPath}/S100/python-metadata.json`);
|
||||
const javaMetadata = JSON.parse(javaStrMetadata.toString());
|
||||
const pythonMetadata = JSON.parse(pythonStrMetadata.toString());
|
||||
expect(pythonMetadata).toMatchObject({
|
||||
title: 'Rule S100',
|
||||
languagesSupport: [
|
||||
{name: 'java', status: 'ready'},
|
||||
{name: 'python', status: 'closed'}
|
||||
]
|
||||
});
|
||||
|
||||
expect(javaMetadata.languagesSupport).toStrictEqual(pythonMetadata.languagesSupport);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('generates only requested rules if a list of rule is provided', () => {
|
||||
return withTestDir((srcPath) => {
|
||||
createFiles(srcPath, {
|
||||
@ -50,7 +83,7 @@ describe('metadata generation', () => {
|
||||
}),
|
||||
});
|
||||
return withTestDir(async (dstPath) => {
|
||||
generate_rules_metadata(srcPath, dstPath, ['S100']);
|
||||
generateRulesMetadata(srcPath, dstPath, ['S100']);
|
||||
|
||||
const s100Exists = fs.existsSync(`${dstPath}/S100/java-metadata.json`);
|
||||
expect(s100Exists).toBeTruthy();
|
||||
@ -72,7 +105,7 @@ describe('metadata generation', () => {
|
||||
}),
|
||||
});
|
||||
return withTestDir(async (dstPath) => {
|
||||
generate_one_rule_metadata(path.join(srcPath, 'S100'), path.join(dstPath, 'S100'), 'master');
|
||||
generateOneRuleMetadata(path.join(srcPath, 'S100'), path.join(dstPath, 'S100'), 'master');
|
||||
|
||||
const s100StrMetadata = fs.readFileSync(`${dstPath}/S100/java-metadata.json`);
|
||||
const s100Metadata = JSON.parse(s100StrMetadata.toString());
|
||||
@ -80,7 +113,7 @@ describe('metadata generation', () => {
|
||||
expect(s100Metadata.branch).toEqual('master');
|
||||
expect(Object.keys(s100Metadata)).not.toContain('prUrl');
|
||||
|
||||
generate_one_rule_metadata(path.join(srcPath, 'S200'), path.join(dstPath, 'S200'), 'add-my-rule', 'https://some.pr/url');
|
||||
generateOneRuleMetadata(path.join(srcPath, 'S200'), path.join(dstPath, 'S200'), 'add-my-rule', 'https://some.pr/url');
|
||||
|
||||
|
||||
const s200StrMetadata = fs.readFileSync(`${dstPath}/S200/java-metadata.json`);
|
||||
@ -91,4 +124,25 @@ describe('metadata generation', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('generate test metadata', () => {
|
||||
return withTestDir(async (dstPath) => {
|
||||
generateRulesMetadata(path.join(__dirname, 'resources', 'rules'), dstPath);
|
||||
const rules = fs.readdirSync(dstPath);
|
||||
expect(rules.length).toEqual(3);
|
||||
let treated = 0;
|
||||
rules.forEach(ruleDir => {
|
||||
const languages = fs.readdirSync(`${dstPath}/${ruleDir}`);
|
||||
expect(languages.length).toBeGreaterThanOrEqual(1);
|
||||
languages.forEach(file => {
|
||||
const actual = JSON.parse(fs.readFileSync(`${dstPath}/${ruleDir}/${file}`).toString());
|
||||
const expectedPath = path.join(__dirname, 'resources', 'metadata', ruleDir, file);
|
||||
const expected = JSON.parse(fs.readFileSync(expectedPath).toString());
|
||||
expect(actual).toStrictEqual(expected);
|
||||
treated++;
|
||||
})
|
||||
});
|
||||
expect(treated).toBe(9);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -2,6 +2,7 @@
|
||||
"title": "Header files should not contain unnamed namespaces",
|
||||
"type": "CODE_SMELL",
|
||||
"status": "ready",
|
||||
"quickfix": "unknown",
|
||||
"remediation": {
|
||||
"func": "Constant/Issue",
|
||||
"constantCost": "1h"
|
||||
@ -9,12 +10,12 @@
|
||||
"tags": [
|
||||
"cert",
|
||||
"misra-c++2008",
|
||||
"clumsy",
|
||||
"pitfall"
|
||||
],
|
||||
"extra": {
|
||||
"coveredLanguages": [
|
||||
"C++",
|
||||
"C"
|
||||
"legacyKeys": [
|
||||
"UnnamedNamespaceInHeader"
|
||||
],
|
||||
"replacementRules": []
|
||||
},
|
||||
@ -22,12 +23,20 @@
|
||||
"ruleSpecification": "RSPEC-1000",
|
||||
"sqKey": "UnnamedNamespaceInHeader",
|
||||
"scope": "Main",
|
||||
"securityStandards": {
|
||||
"CERT": [
|
||||
"DCL59-CPP."
|
||||
]
|
||||
},
|
||||
"defaultQualityProfiles": [
|
||||
"Sonar way",
|
||||
"MISRA C++ 2008 recommended"
|
||||
],
|
||||
"all_languages": [
|
||||
"cfamily"
|
||||
"languagesSupport": [
|
||||
{
|
||||
"name": "cfamily",
|
||||
"status": "ready"
|
||||
}
|
||||
],
|
||||
"allKeys": [
|
||||
"UnnamedNamespaceInHeader"
|
@ -0,0 +1,85 @@
|
||||
<div class="paragraph">
|
||||
<p>An unnamed namespace will be unique within each translation unit. Any declarations appearing in an unnamed namespace in a header will refer to a different entity in each translation unit, which is probably not the expected behavior.</p>
|
||||
</div>
|
||||
<div class="sect1">
|
||||
<h2 id="_noncompliant_code_example">Noncompliant Code Example</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>// Header.hpp
|
||||
namespace // Noncompliant
|
||||
{
|
||||
extern int32_t x;
|
||||
}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>// File1.cpp
|
||||
#include "Header.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
int32_t x;
|
||||
}
|
||||
|
||||
void fn_a(void)
|
||||
{
|
||||
x = 42;
|
||||
}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>// File2.cpp
|
||||
#include "Header.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
int32_t x; // this is a different x than in File1.cpp
|
||||
}
|
||||
|
||||
void fn_b(void)
|
||||
{
|
||||
fn_a(); // Is expected to initialize "x" to 42
|
||||
if (x == 42) // But does not, as there are 2 distinct "x" variables
|
||||
{
|
||||
}
|
||||
}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect1">
|
||||
<h2 id="_see">See</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="ulist">
|
||||
<ul>
|
||||
<li>
|
||||
<p>MISRA C++:2008, 7-3-3 - There shall be no unnamed namespaces in header files.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://wiki.sei.cmu.edu/confluence/x/VXs-BQ">CERT, DCL59-CPP.</a> - Do not define an unnamed namespace in a header file</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<hr/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect1">
|
||||
<h2 id="_comments_and_links">Comments And Links</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph">
|
||||
<p>(visible only on this page)</p>
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_on_8_feb_2018_004304_thomas_epperson_wrote">on 8 Feb 2018, 00:43:04 Thomas Epperson wrote:</h3>
|
||||
<div class="paragraph">
|
||||
<p>The implementation incorrectly flags unnamed namespaces in source files. The specs only refer to unnamed namespaces in HEADER files.</p>
|
||||
</div>
|
||||
<div class="paragraph">
|
||||
<p>See reported bugs in https://sonarcloud.io/dashboard?id=uglyoldbob_decompiler%3Arestructure</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,45 @@
|
||||
{
|
||||
"title": "Header files should not contain unnamed namespaces",
|
||||
"type": "CODE_SMELL",
|
||||
"status": "ready",
|
||||
"remediation": {
|
||||
"func": "Constant/Issue",
|
||||
"constantCost": "1h"
|
||||
},
|
||||
"tags": [
|
||||
"cert",
|
||||
"misra-c++2008",
|
||||
"clumsy",
|
||||
"pitfall"
|
||||
],
|
||||
"extra": {
|
||||
"replacementRules": [],
|
||||
"legacyKeys": [
|
||||
"UnnamedNamespaceInHeader"
|
||||
]
|
||||
},
|
||||
"defaultSeverity": "Major",
|
||||
"ruleSpecification": "RSPEC-1000",
|
||||
"sqKey": "UnnamedNamespaceInHeader",
|
||||
"scope": "Main",
|
||||
"securityStandards": {
|
||||
"CERT": [
|
||||
"DCL59-CPP."
|
||||
]
|
||||
},
|
||||
"defaultQualityProfiles": [
|
||||
"Sonar way",
|
||||
"MISRA C++ 2008 recommended"
|
||||
],
|
||||
"quickfix": "unknown",
|
||||
"languagesSupport": [
|
||||
{
|
||||
"name": "cfamily",
|
||||
"status": "ready"
|
||||
}
|
||||
],
|
||||
"allKeys": [
|
||||
"UnnamedNamespaceInHeader"
|
||||
],
|
||||
"branch": "master"
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
{
|
||||
"title": "Printf-style format strings should be used correctly",
|
||||
"type": "CODE_SMELL",
|
||||
"status": "ready",
|
||||
"remediation": {
|
||||
"func": "Constant\/Issue",
|
||||
"constantCost": "10min"
|
||||
},
|
||||
"tags": [
|
||||
"cert",
|
||||
"confusing",
|
||||
"clumsy"
|
||||
],
|
||||
"defaultQualityProfiles": [
|
||||
"Sonar way",
|
||||
"MISRA C++ 2008 recommended"
|
||||
],
|
||||
"allKeys": [
|
||||
"S3457"
|
||||
],
|
||||
"branch": "master",
|
||||
"defaultSeverity": "Minor",
|
||||
"extra": {
|
||||
"legacyKeys": [],
|
||||
"replacementRules": []
|
||||
},
|
||||
"quickfix": "unknown",
|
||||
"ruleSpecification": "RSPEC-3457",
|
||||
"sqKey": "S3457",
|
||||
"scope": "All",
|
||||
"securityStandards": {
|
||||
"CERT": [
|
||||
"FIO47-C."
|
||||
]
|
||||
},
|
||||
"languagesSupport": [
|
||||
{
|
||||
"name": "cfamily",
|
||||
"status": "ready"
|
||||
},
|
||||
{
|
||||
"name": "csharp",
|
||||
"status": "ready"
|
||||
},
|
||||
{
|
||||
"name": "java",
|
||||
"status": "closed"
|
||||
},
|
||||
{
|
||||
"name": "python",
|
||||
"status": "deprecated"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
{
|
||||
"title": "Composite format strings should be used correctly",
|
||||
"type": "CODE_SMELL",
|
||||
"status": "ready",
|
||||
"remediation": {
|
||||
"func": "Constant\/Issue",
|
||||
"constantCost": "10min"
|
||||
},
|
||||
"tags": [
|
||||
"confusing"
|
||||
],
|
||||
"defaultQualityProfiles": [
|
||||
"Sonar way"
|
||||
],
|
||||
"allKeys": [
|
||||
"S3457"
|
||||
],
|
||||
"branch": "master",
|
||||
"defaultSeverity": "Major",
|
||||
"extra": {
|
||||
"legacyKeys": [],
|
||||
"replacementRules": []
|
||||
},
|
||||
"quickfix": "unknown",
|
||||
"ruleSpecification": "RSPEC-3457",
|
||||
"sqKey": "S3457",
|
||||
"scope": "All",
|
||||
"languagesSupport": [
|
||||
{
|
||||
"name": "cfamily",
|
||||
"status": "ready"
|
||||
},
|
||||
{
|
||||
"name": "csharp",
|
||||
"status": "ready"
|
||||
},
|
||||
{
|
||||
"name": "java",
|
||||
"status": "closed"
|
||||
},
|
||||
{
|
||||
"name": "python",
|
||||
"status": "deprecated"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
<div class="paragraph">
|
||||
<p>Because <code>printf</code> format strings are interpreted at runtime, rather than validated by the compiler, they can contain errors that result in the wrong strings being created. This rule statically validates the correlation of <code>printf</code> format strings to their arguments.</p>
|
||||
</div>
|
||||
<div class="paragraph">
|
||||
<p>The related rule S2275 is about errors that will create undefined behavior, while this rule is about errors that produce an unexpected string.</p>
|
||||
</div>
|
||||
<div class="sect1">
|
||||
<h2 id="_noncompliant_code_example">Noncompliant Code Example</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>printf("%d", 1, 2); // Noncompliant; the second argument "2" is unused
|
||||
printf("%0-f", 1.2); // Noncompliant; flag "0" is ignored because of "-"</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect1">
|
||||
<h2 id="_compliant_solution">Compliant Solution</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>printf("%d %d", 1, 2); // Compliant
|
||||
printf("%-f", 1.2); // Compliant</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect1">
|
||||
<h2 id="_exceptions">Exceptions</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph">
|
||||
<p>This rule will only work if the format string is provided as a string literal.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect1">
|
||||
<h2 id="_see">See</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="ulist">
|
||||
<ul>
|
||||
<li>
|
||||
<p><a href="https://wiki.sei.cmu.edu/confluence/x/J9YxBQ">CERT, FIO47-C.</a> - Use valid format strings</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<hr/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect1">
|
||||
<h2 id="_implementation_specification">Implementation Specification</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph">
|
||||
<p>(visible only on this page)</p>
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_message">Message</h3>
|
||||
<div class="paragraph">
|
||||
<p>XXXX</p>
|
||||
</div>
|
||||
<hr/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect1">
|
||||
<h2 id="_comments_and_links">Comments And Links</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph">
|
||||
<p>(visible only on this page)</p>
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_is_duplicated_by_s3941">is duplicated by: S3941</h3>
|
||||
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_is_related_to_s2275">is related to: S2275</h3>
|
||||
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_on_10_dec_2015_090759_tamas_vajk_wrote">on 10 Dec 2015, 09:07:59 Tamas Vajk wrote:</h3>
|
||||
<div class="paragraph">
|
||||
<p>\[~ann.campbell.2] Removed the performance label, as the performance impact is insignificant.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_on_10_dec_2015_144405_ann_campbell_wrote">on 10 Dec 2015, 14:44:05 Ann Campbell wrote:</h3>
|
||||
<div class="paragraph">
|
||||
<p>I’ve updated SQALE characteristic to match [~tamas.vajk]</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,54 @@
|
||||
{
|
||||
"title": "Printf-style format strings should be used correctly",
|
||||
"type": "CODE_SMELL",
|
||||
"status": "ready",
|
||||
"remediation": {
|
||||
"func": "Constant/Issue",
|
||||
"constantCost": "10min"
|
||||
},
|
||||
"tags": [
|
||||
"cert",
|
||||
"confusing",
|
||||
"clumsy"
|
||||
],
|
||||
"extra": {
|
||||
"replacementRules": [],
|
||||
"legacyKeys": []
|
||||
},
|
||||
"defaultSeverity": "Minor",
|
||||
"ruleSpecification": "RSPEC-3457",
|
||||
"sqKey": "S3457",
|
||||
"scope": "All",
|
||||
"defaultQualityProfiles": [
|
||||
"Sonar way",
|
||||
"MISRA C++ 2008 recommended"
|
||||
],
|
||||
"quickfix": "unknown",
|
||||
"securityStandards": {
|
||||
"CERT": [
|
||||
"FIO47-C."
|
||||
]
|
||||
},
|
||||
"languagesSupport": [
|
||||
{
|
||||
"name": "cfamily",
|
||||
"status": "ready"
|
||||
},
|
||||
{
|
||||
"name": "csharp",
|
||||
"status": "ready"
|
||||
},
|
||||
{
|
||||
"name": "java",
|
||||
"status": "closed"
|
||||
},
|
||||
{
|
||||
"name": "python",
|
||||
"status": "deprecated"
|
||||
}
|
||||
],
|
||||
"allKeys": [
|
||||
"S3457"
|
||||
],
|
||||
"branch": "master"
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
{
|
||||
"title": "Composite format strings should be used correctly",
|
||||
"type": "CODE_SMELL",
|
||||
"status": "closed",
|
||||
"remediation": {
|
||||
"func": "Constant\/Issue",
|
||||
"constantCost": "10min"
|
||||
},
|
||||
"allKeys": [
|
||||
"S3457"
|
||||
],
|
||||
"tags": [
|
||||
"cert",
|
||||
"confusing"
|
||||
],
|
||||
"branch": "master",
|
||||
"defaultQualityProfiles": [
|
||||
"Sonar way"
|
||||
],
|
||||
"defaultSeverity": "Major",
|
||||
"extra": {
|
||||
"legacyKeys": [],
|
||||
"replacementRules": []
|
||||
},
|
||||
"quickfix": "unknown",
|
||||
"securityStandards": {
|
||||
"CERT": [
|
||||
"FIO47-C."
|
||||
]
|
||||
}, "ruleSpecification": "RSPEC-3457",
|
||||
"sqKey": "S3457",
|
||||
"scope": "All",
|
||||
"languagesSupport": [
|
||||
{
|
||||
"name": "cfamily",
|
||||
"status": "ready"
|
||||
},
|
||||
{
|
||||
"name": "csharp",
|
||||
"status": "ready"
|
||||
},
|
||||
{
|
||||
"name": "java",
|
||||
"status": "closed"
|
||||
},
|
||||
{
|
||||
"name": "python",
|
||||
"status": "deprecated"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
{
|
||||
"title": "String formatting should be used correctly",
|
||||
"type": "CODE_SMELL",
|
||||
"status": "deprecated",
|
||||
"remediation": {
|
||||
"func": "Constant\/Issue",
|
||||
"constantCost": "10min"
|
||||
},
|
||||
"tags": [
|
||||
"confusing"
|
||||
],
|
||||
"allKeys": [
|
||||
"S3457"
|
||||
],
|
||||
"defaultSeverity": "Major",
|
||||
"ruleSpecification": "RSPEC-3457",
|
||||
"sqKey": "S3457",
|
||||
"scope": "All",
|
||||
"branch": "master",
|
||||
"defaultQualityProfiles": [
|
||||
"Sonar way"
|
||||
],
|
||||
"extra": {
|
||||
"legacyKeys": [],
|
||||
"replacementRules": []
|
||||
},
|
||||
"quickfix": "unknown",
|
||||
"languagesSupport": [
|
||||
{
|
||||
"name": "cfamily",
|
||||
"status": "ready"
|
||||
},
|
||||
{
|
||||
"name": "csharp",
|
||||
"status": "ready"
|
||||
},
|
||||
{
|
||||
"name": "java",
|
||||
"status": "closed"
|
||||
},
|
||||
{
|
||||
"name": "python",
|
||||
"status": "deprecated"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
{
|
||||
"title": "\"<signal.h>\" should not be used",
|
||||
"type": "BUG",
|
||||
"status": "ready",
|
||||
"remediation": {
|
||||
"func": "Constant\/Issue",
|
||||
"constantCost": "4h"
|
||||
},
|
||||
"allKeys": [
|
||||
"PPIncludeSignal"
|
||||
],
|
||||
"tags": [
|
||||
"based-on-misra",
|
||||
"lock-in"
|
||||
],
|
||||
"defaultSeverity": "Critical",
|
||||
"branch": "master",
|
||||
"defaultQualityProfiles": [],
|
||||
"extra": {
|
||||
"legacyKeys": [
|
||||
"PPIncludeSignal"
|
||||
],
|
||||
"replacementRules": []
|
||||
},
|
||||
"quickfix": "unknown",
|
||||
"ruleSpecification": "RSPEC-987",
|
||||
"sqKey": "PPIncludeSignal",
|
||||
"scope": "Main",
|
||||
"languagesSupport": [
|
||||
{
|
||||
"name": "cfamily",
|
||||
"status": "ready"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
<div class="paragraph">
|
||||
<p>Signal handling contains implementation-defined and undefined behavior.</p>
|
||||
</div>
|
||||
<div class="sect1">
|
||||
<h2 id="_noncompliant_code_example">Noncompliant Code Example</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>#include <signal.h> /* Noncompliant */</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect1">
|
||||
<h2 id="_see">See</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="ulist">
|
||||
<ul>
|
||||
<li>
|
||||
<p>MISRA C:2004, 20.8 - The signal handling facilities of <signal.h> shall not be used.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>MISRA C:2012, 21.5 - The standard header file <signal.h> shall not be used</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<hr/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect1">
|
||||
<h2 id="_comments_and_links">Comments And Links</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph">
|
||||
<p>(visible only on this page)</p>
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_is_related_to_s1054">is related to: S1054</h3>
|
||||
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_on_31_mar_2015_190723_evgeny_mandrikov_wrote">on 31 Mar 2015, 19:07:23 Evgeny Mandrikov wrote:</h3>
|
||||
<div class="paragraph">
|
||||
<p>\[~ann.campbell.2] implementation seems more complete (SQALE, description) than this spec.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,35 @@
|
||||
{
|
||||
"title": "\"<signal.h>\" should not be used",
|
||||
"type": "BUG",
|
||||
"status": "ready",
|
||||
"remediation": {
|
||||
"func": "Constant/Issue",
|
||||
"constantCost": "4h"
|
||||
},
|
||||
"tags": [
|
||||
"based-on-misra",
|
||||
"lock-in"
|
||||
],
|
||||
"extra": {
|
||||
"replacementRules": [],
|
||||
"legacyKeys": [
|
||||
"PPIncludeSignal"
|
||||
]
|
||||
},
|
||||
"defaultSeverity": "Critical",
|
||||
"ruleSpecification": "RSPEC-987",
|
||||
"sqKey": "PPIncludeSignal",
|
||||
"scope": "Main",
|
||||
"defaultQualityProfiles": [],
|
||||
"quickfix": "unknown",
|
||||
"languagesSupport": [
|
||||
{
|
||||
"name": "cfamily",
|
||||
"status": "ready"
|
||||
}
|
||||
],
|
||||
"allKeys": [
|
||||
"PPIncludeSignal"
|
||||
],
|
||||
"branch": "master"
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
{
|
||||
"title": "Printf-style format strings should be used correctly",
|
||||
"type": "CODE_SMELL",
|
||||
"status": "ready",
|
||||
"remediation": {
|
||||
"func": "Constant\/Issue",
|
||||
"constantCost": "10min"
|
||||
},
|
||||
"tags": [
|
||||
"cert",
|
||||
"confusing"
|
||||
],
|
||||
"defaultSeverity": "Major",
|
||||
"ruleSpecification": "RSPEC-3457",
|
||||
"sqKey": "S3457",
|
||||
"scope": "All"
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
{
|
||||
"title": "String formatting should be used correctly",
|
||||
"type": "CODE_SMELL",
|
||||
"status": "ready",
|
||||
"remediation": {
|
||||
"func": "Constant\/Issue",
|
||||
"constantCost": "10min"
|
||||
},
|
||||
"tags": [
|
||||
"confusing"
|
||||
],
|
||||
"defaultSeverity": "Major",
|
||||
"ruleSpecification": "RSPEC-3457",
|
||||
"sqKey": "S3457",
|
||||
"scope": "All"
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
=== on 8 Feb 2018, 00:43:04 Thomas Epperson wrote:
|
||||
The implementation incorrectly flags unnamed namespaces in source files. The specs only refer to unnamed namespaces in HEADER files.
|
||||
|
||||
|
||||
See reported bugs in \https://sonarcloud.io/dashboard?id=uglyoldbob_decompiler%3Arestructure
|
||||
|
||||
|
||||
|
@ -0,0 +1,37 @@
|
||||
{
|
||||
"title": "Header files should not contain unnamed namespaces",
|
||||
"type": "CODE_SMELL",
|
||||
"status": "ready",
|
||||
"remediation": {
|
||||
"func": "Constant\/Issue",
|
||||
"constantCost": "1h"
|
||||
},
|
||||
"tags": [
|
||||
"cert",
|
||||
"misra-c++2008",
|
||||
"clumsy",
|
||||
"pitfall"
|
||||
],
|
||||
"extra": {
|
||||
"replacementRules": [
|
||||
|
||||
],
|
||||
"legacyKeys": [
|
||||
"UnnamedNamespaceInHeader"
|
||||
]
|
||||
},
|
||||
"defaultSeverity": "Major",
|
||||
"ruleSpecification": "RSPEC-1000",
|
||||
"sqKey": "UnnamedNamespaceInHeader",
|
||||
"scope": "Main",
|
||||
"securityStandards": {
|
||||
"CERT": [
|
||||
"DCL59-CPP."
|
||||
]
|
||||
},
|
||||
"defaultQualityProfiles": [
|
||||
"Sonar way",
|
||||
"MISRA C++ 2008 recommended"
|
||||
],
|
||||
"quickfix": "unknown"
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
An unnamed namespace will be unique within each translation unit. Any declarations appearing in an unnamed namespace in a header will refer to a different entity in each translation unit, which is probably not the expected behavior.
|
||||
|
||||
|
||||
== Noncompliant Code Example
|
||||
|
||||
----
|
||||
// Header.hpp
|
||||
namespace // Noncompliant
|
||||
{
|
||||
extern int32_t x;
|
||||
}
|
||||
----
|
||||
|
||||
----
|
||||
// File1.cpp
|
||||
#include "Header.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
int32_t x;
|
||||
}
|
||||
|
||||
void fn_a(void)
|
||||
{
|
||||
x = 42;
|
||||
}
|
||||
----
|
||||
|
||||
----
|
||||
// File2.cpp
|
||||
#include "Header.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
int32_t x; // this is a different x than in File1.cpp
|
||||
}
|
||||
|
||||
void fn_b(void)
|
||||
{
|
||||
fn_a(); // Is expected to initialize "x" to 42
|
||||
if (x == 42) // But does not, as there are 2 distinct "x" variables
|
||||
{
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
== See
|
||||
|
||||
* MISRA {cpp}:2008, 7-3-3 - There shall be no unnamed namespaces in header files.
|
||||
* https://wiki.sei.cmu.edu/confluence/x/VXs-BQ[CERT, DCL59-CPP.] - Do not define an unnamed namespace in a header file
|
||||
|
||||
|
||||
ifdef::env-github,rspecator-view[]
|
||||
'''
|
||||
== Comments And Links
|
||||
(visible only on this page)
|
||||
|
||||
include::comments-and-links.adoc[]
|
||||
endif::env-github,rspecator-view[]
|
@ -0,0 +1,2 @@
|
||||
{
|
||||
}
|
@ -1,21 +1,18 @@
|
||||
{
|
||||
"title": "Printf-style format strings should be used correctly",
|
||||
"type": "CODE_SMELL",
|
||||
"status": "ready",
|
||||
"remediation": {
|
||||
"func": "Constant\/Issue",
|
||||
"constantCost": "10min"
|
||||
},
|
||||
"tags": [
|
||||
"cert",
|
||||
"confusing",
|
||||
"clumsy"
|
||||
],
|
||||
"defaultQualityProfiles": [
|
||||
"Sonar way",
|
||||
"MISRA C++ 2008 recommended"
|
||||
],
|
||||
"defaultSeverity": "Minor",
|
||||
"ruleSpecification": "RSPEC-3457",
|
||||
"sqKey": "S3457",
|
||||
"scope": "All"
|
||||
"securityStandards": {
|
||||
"CERT": [
|
||||
"FIO47-C."
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
Because ``++printf++`` format strings are interpreted at runtime, rather than validated by the compiler, they can contain errors that result in the wrong strings being created. This rule statically validates the correlation of ``++printf++`` format strings to their arguments.
|
||||
|
||||
|
||||
The related rule S2275 is about errors that will create undefined behavior, while this rule is about errors that produce an unexpected string.
|
||||
|
||||
== Noncompliant Code Example
|
||||
|
||||
----
|
||||
printf("%d", 1, 2); // Noncompliant; the second argument "2" is unused
|
||||
printf("%0-f", 1.2); // Noncompliant; flag "0" is ignored because of "-"
|
||||
----
|
||||
|
||||
== Compliant Solution
|
||||
|
||||
----
|
||||
printf("%d %d", 1, 2); // Compliant
|
||||
printf("%-f", 1.2); // Compliant
|
||||
----
|
||||
|
||||
== Exceptions
|
||||
|
||||
This rule will only work if the format string is provided as a string literal.
|
||||
|
||||
== See
|
||||
|
||||
* https://wiki.sei.cmu.edu/confluence/x/J9YxBQ[CERT, FIO47-C.] - Use valid format strings
|
||||
|
||||
ifdef::env-github,rspecator-view[]
|
||||
|
||||
'''
|
||||
== Implementation Specification
|
||||
(visible only on this page)
|
||||
|
||||
include::../message.adoc[]
|
||||
|
||||
'''
|
||||
== Comments And Links
|
||||
(visible only on this page)
|
||||
|
||||
include::../comments-and-links.adoc[]
|
||||
endif::env-github,rspecator-view[]
|
@ -0,0 +1,10 @@
|
||||
=== is duplicated by: S3941
|
||||
|
||||
=== is related to: S2275
|
||||
|
||||
=== on 10 Dec 2015, 09:07:59 Tamas Vajk wrote:
|
||||
\[~ann.campbell.2] Removed the performance label, as the performance impact is insignificant.
|
||||
|
||||
=== on 10 Dec 2015, 14:44:05 Ann Campbell wrote:
|
||||
I've updated SQALE characteristic to match [~tamas.vajk]
|
||||
|
@ -0,0 +1,29 @@
|
||||
== Compliant Solution
|
||||
|
||||
----
|
||||
String.format("First %s and then %s", "foo", "bar");
|
||||
String.format("Display %2$d and then %d", 1, 3);
|
||||
String.format("Too many arguments %d %d", 1, 2);
|
||||
String.format("First Line%n");
|
||||
String.format("Is myObject null ? %b", myObject == null);
|
||||
String.format("value is %d", value);
|
||||
String s = "string without arguments";
|
||||
|
||||
MessageFormat.format("Result {0}.", value);
|
||||
MessageFormat.format("Result '{0}' = {0}", value);
|
||||
MessageFormat.format("Result {0}.", myObject);
|
||||
|
||||
java.util.Logger logger;
|
||||
logger.log(java.util.logging.Level.SEVERE, "Result {0}.", myObject);
|
||||
logger.log(java.util.logging.Level.SEVERE, "Result {0}'", 14);
|
||||
logger.log(java.util.logging.Level.SEVERE, exception, () -> "Result " + param);
|
||||
|
||||
org.slf4j.Logger slf4jLog;
|
||||
org.slf4j.Marker marker;
|
||||
|
||||
slf4jLog.debug(marker, "message {}");
|
||||
slf4jLog.debug(marker, "message {}", 1);
|
||||
|
||||
org.apache.logging.log4j.Logger log4jLog;
|
||||
log4jLog.debug("message {}", 1);
|
||||
----
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"title": "Composite format strings should be used correctly"
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
Because composite format strings are interpreted at runtime, rather than validated by the compiler, they can contain errors that lead to unexpected behaviors or runtime errors. This rule statically validates the good behavior of composite formats when calling the methods of ``++String.Format++``, ``++StringBuilder.AppendFormat++``, ``++Console.Write++``, ``++Console.WriteLine++``, ``++TextWriter.Write++``, ``++TextWriter.WriteLine++``, ``++Debug.WriteLine(String, Object[])++``, ``++Trace.TraceError(String, Object[])++``, ``++Trace.TraceInformation(String, Object[])++``, ``++Trace.TraceWarning(String, Object[])++`` and ``++TraceSource.TraceInformation(String, Object[])++``.
|
||||
|
||||
== Noncompliant Code Example
|
||||
|
||||
----
|
||||
s = string.Format("{0}", arg0, arg1); // Noncompliant, arg1 is declared but not used.
|
||||
s = string.Format("{0} {2}", arg0, arg1, arg2); // Noncompliant, the format item with index 1 is missing so arg1 will not be used.
|
||||
s = string.Format("foo"); // Noncompliant, there is no need to use string.Format here.
|
||||
----
|
||||
|
||||
== Compliant Solution
|
||||
|
||||
----
|
||||
s = string.Format("{0}", arg0);
|
||||
s = string.Format("{0} {1}", arg0, arg2);
|
||||
s = "foo";
|
||||
----
|
||||
|
||||
== Exceptions
|
||||
|
||||
* No issue is raised if the format string is not a ``++const++``.
|
||||
|
||||
----
|
||||
var pattern = "{0} {1} {2}";
|
||||
var res = string.Format(pattern, 1, 2); // Compliant, not const string are not recognized
|
||||
----
|
||||
|
||||
* No issue is raised if the argument is not an inline creation array.
|
||||
|
||||
----
|
||||
var array = new int[] {};
|
||||
var res = string.Format("{0} {1}", array); // Compliant we don't know the size of the array
|
||||
----
|
||||
|
||||
* This rule doesn't check whether the format specifier (defined after the ``++:++``) is actually valid.
|
||||
|
||||
|
||||
ifdef::env-github,rspecator-view[]
|
||||
|
||||
'''
|
||||
== Implementation Specification
|
||||
(visible only on this page)
|
||||
|
||||
include::../message.adoc[]
|
||||
|
||||
'''
|
||||
== Comments And Links
|
||||
(visible only on this page)
|
||||
|
||||
include::../comments-and-links.adoc[]
|
||||
endif::env-github,rspecator-view[]
|
@ -0,0 +1 @@
|
||||
Because ``++printf++``-style format strings are interpreted at runtime, rather than validated by the compiler, they can contain errors that result in the wrong strings being created. This rule statically validates the correlation of ``++printf++``-style format strings to their arguments when calling the ``++format(...)++`` methods of ``++java.util.Formatter++``, ``++java.lang.String++``, ``++java.io.PrintStream++``, ``++MessageFormat++``, and ``++java.io.PrintWriter++`` classes and the ``++printf(...)++`` methods of ``++java.io.PrintStream++`` or ``++java.io.PrintWriter++`` classes.
|
@ -0,0 +1,12 @@
|
||||
{
|
||||
"tags": [
|
||||
"cert",
|
||||
"confusing"
|
||||
],
|
||||
"status": "closed",
|
||||
"securityStandards": {
|
||||
"CERT": [
|
||||
"FIO47-C."
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
include::../description.adoc[]
|
||||
|
||||
include::../noncompliant.adoc[]
|
||||
|
||||
include::../compliant.adoc[]
|
||||
|
||||
== See
|
||||
|
||||
* https://wiki.sei.cmu.edu/confluence/x/J9YxBQ[CERT, FIO47-C.] - Use valid format strings
|
||||
|
||||
ifdef::env-github,rspecator-view[]
|
||||
|
||||
'''
|
||||
== Implementation Specification
|
||||
(visible only on this page)
|
||||
|
||||
include::../message.adoc[]
|
||||
|
||||
'''
|
||||
== Comments And Links
|
||||
(visible only on this page)
|
||||
|
||||
include::../comments-and-links.adoc[]
|
||||
endif::env-github,rspecator-view[]
|
@ -0,0 +1,4 @@
|
||||
=== Message
|
||||
|
||||
XXXX
|
||||
|
@ -16,13 +16,12 @@
|
||||
"ruleSpecification": "RSPEC-3457",
|
||||
"sqKey": "S3457",
|
||||
"scope": "All",
|
||||
"all_languages": [
|
||||
"cfamily",
|
||||
"csharp",
|
||||
"java",
|
||||
"python"
|
||||
],
|
||||
"allKeys": [
|
||||
"S3457"
|
||||
]
|
||||
],
|
||||
"extra": {
|
||||
"replacementRules": [],
|
||||
"legacyKeys": []
|
||||
},
|
||||
"quickfix": "unknown"
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
== Noncompliant Code Example
|
||||
|
||||
----
|
||||
String.format("First {0} and then {1}", "foo", "bar"); //Noncompliant. Looks like there is a confusion with the use of {{java.text.MessageFormat}}, parameters "foo" and "bar" will be simply ignored here
|
||||
String.format("Display %3$d and then %d", 1, 2, 3); //Noncompliant; the second argument '2' is unused
|
||||
String.format("Too many arguments %d and %d", 1, 2, 3); //Noncompliant; the third argument '3' is unused
|
||||
String.format("First Line\n"); //Noncompliant; %n should be used in place of \n to produce the platform-specific line separator
|
||||
String.format("Is myObject null ? %b", myObject); //Noncompliant; when a non-boolean argument is formatted with %b, it prints true for any nonnull value, and false for null. Even if intended, this is misleading. It's better to directly inject the boolean value (myObject == null in this case)
|
||||
String.format("value is " + value); // Noncompliant
|
||||
String s = String.format("string without arguments"); // Noncompliant
|
||||
|
||||
MessageFormat.format("Result '{0}'.", value); // Noncompliant; String contains no format specifiers. (quote are discarding format specifiers)
|
||||
MessageFormat.format("Result {0}.", value, value); // Noncompliant; 2nd argument is not used
|
||||
MessageFormat.format("Result {0}.", myObject.toString()); // Noncompliant; no need to call toString() on objects
|
||||
|
||||
java.util.Logger logger;
|
||||
logger.log(java.util.logging.Level.SEVERE, "Result {0}.", myObject.toString()); // Noncompliant; no need to call toString() on objects
|
||||
logger.log(java.util.logging.Level.SEVERE, "Result.", new Exception()); // compliant, parameter is an exception
|
||||
logger.log(java.util.logging.Level.SEVERE, "Result '{0}'", 14); // Noncompliant - String contains no format specifiers.
|
||||
logger.log(java.util.logging.Level.SEVERE, "Result " + param, exception); // Noncompliant; Lambda should be used to differ string concatenation.
|
||||
|
||||
org.slf4j.Logger slf4jLog;
|
||||
org.slf4j.Marker marker;
|
||||
|
||||
slf4jLog.debug(marker, "message {}");
|
||||
slf4jLog.debug(marker, "message", 1); // Noncompliant - String contains no format specifiers.
|
||||
|
||||
org.apache.logging.log4j.Logger log4jLog;
|
||||
log4jLog.debug("message", 1); // Noncompliant - String contains no format specifiers.
|
||||
----
|
@ -0,0 +1,19 @@
|
||||
=== Message
|
||||
|
||||
* Add replacement fields or use a normal string instead of an f-string.
|
||||
* Remove this unused argument.
|
||||
* Remove X unused positional arguments.
|
||||
* Fix this formatted string's syntax.
|
||||
* Name unnamed replacement field(s).
|
||||
* Replace formatting argument(s) with a mapping; Replacement fields are named.
|
||||
* Use only positional or named fields, don't mix them.
|
||||
* Replace this value with a number as "%d" requires.
|
||||
* Replace this value with an integer as "%X" requires.
|
||||
* Replace this value with an integer as "*" requires.
|
||||
* Add X missing argument(s).
|
||||
* Remove X unexpected argument(s); format string expects Y arguments.
|
||||
* Replace this key; %-format accepts only string keys.
|
||||
* Provide a value for field "X".
|
||||
* Remove this unused argument or add a replacement field.
|
||||
|
||||
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"title": "String formatting should be used correctly",
|
||||
"status": "deprecated"
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
Formatting strings, either with the ``++%++`` operator or ``++str.format++`` method, requires a valid string and arguments matching this string's replacement fields.
|
||||
|
||||
|
||||
This also applies to loggers from the ``++logging++`` module. Internally they use ``++%-formatting++``. The only difference is that they will log an error instead of raising an exception when provided arguments are invalid.
|
||||
|
||||
|
||||
Formatted string literals, also called "f-strings", are generally simpler to use, and any syntax mistake will fail at compile time. However it is easy to forget curly braces and it won't raise any error.
|
||||
|
||||
|
||||
This rule raises an issue when:
|
||||
|
||||
* A string formatted with ``++%++`` will not return the expected string because some arguments are not used.
|
||||
* A string formatted with ``++str.format++`` will not return the expected string because some arguments are not used.
|
||||
* An "f-string" doesn't contain any replacement field, which probably means that some curly braces are missing.
|
||||
* Loggers will log an error because their message is not formatted properly.
|
||||
|
||||
Rule S2275 covers cases where formatting a string will raise an exception.
|
||||
|
||||
== Noncompliant Code Example
|
||||
|
||||
----
|
||||
"Error %(message)s" % {"message": "something failed", "extra": "some dead code"} # Noncompliant. Remove the unused argument "extra" or add a replacement field.
|
||||
|
||||
"Error: User {} has not been able to access []".format("Alice", "MyFile") # Noncompliant. Remove 1 unexpected argument or add a replacement field.
|
||||
|
||||
user = "Alice"
|
||||
resource = "MyFile"
|
||||
message = f"Error: User [user] has not been able to access [resource]" # Noncompliant. Add replacement fields or use a normal string instead of an f-string.
|
||||
|
||||
import logging
|
||||
logging.error("Error: User %s has not been able to access %s", "Alice") # Noncompliant. Add 1 missing argument.
|
||||
----
|
||||
|
||||
== Compliant Solution
|
||||
|
||||
----
|
||||
"Error %(message)s" % {"message": "something failed"}
|
||||
|
||||
"Error: User {} has not been able to access {}".format("Alice", "MyFile")
|
||||
|
||||
user = "Alice"
|
||||
resource = "MyFile"
|
||||
message = f"Error: User {user} has not been able to access {resource}"
|
||||
|
||||
import logging
|
||||
logging.error("Error: User %s has not been able to access %s", "Alice", "MyFile")
|
||||
----
|
||||
|
||||
== See
|
||||
|
||||
* https://docs.python.org/3/library/string.html#format-string-syntax[Python documentation - Format String Syntax]
|
||||
* https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting[Python documentation - printf-style String Formatting]
|
||||
* https://docs.python.org/3/howto/logging.html#loggers[Python documentation - Loggers]
|
||||
* https://docs.python.org/3/howto/logging-cookbook.html#using-particular-formatting-styles-throughout-your-application[Python documentation - Using particular formatting styles throughout your application]
|
||||
* https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals[Python documentation - Formatted string literals]
|
||||
|
||||
ifdef::env-github,rspecator-view[]
|
||||
|
||||
'''
|
||||
== Implementation Specification
|
||||
(visible only on this page)
|
||||
|
||||
include::message.adoc[]
|
||||
|
||||
'''
|
||||
== Comments And Links
|
||||
(visible only on this page)
|
||||
|
||||
include::../comments-and-links.adoc[]
|
||||
endif::env-github,rspecator-view[]
|
@ -0,0 +1,6 @@
|
||||
include::description.adoc[]
|
||||
|
||||
include::noncompliant.adoc[]
|
||||
|
||||
include::compliant.adoc[]
|
||||
|
@ -0,0 +1,5 @@
|
||||
=== is related to: S1054
|
||||
|
||||
=== on 31 Mar 2015, 19:07:23 Evgeny Mandrikov wrote:
|
||||
\[~ann.campbell.2] implementation seems more complete (SQALE, description) than this spec.
|
||||
|
@ -10,8 +10,20 @@
|
||||
"based-on-misra",
|
||||
"lock-in"
|
||||
],
|
||||
"extra": {
|
||||
"replacementRules": [
|
||||
|
||||
],
|
||||
"legacyKeys": [
|
||||
"PPIncludeSignal"
|
||||
]
|
||||
},
|
||||
"defaultSeverity": "Critical",
|
||||
"ruleSpecification": "RSPEC-987",
|
||||
"sqKey": "PPIncludeSignal",
|
||||
"scope": "Main"
|
||||
"scope": "Main",
|
||||
"defaultQualityProfiles": [
|
||||
|
||||
],
|
||||
"quickfix": "unknown"
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
Signal handling contains implementation-defined and undefined behavior.
|
||||
|
||||
|
||||
== Noncompliant Code Example
|
||||
|
||||
----
|
||||
#include <signal.h> /* Noncompliant */
|
||||
----
|
||||
|
||||
|
||||
== See
|
||||
|
||||
* MISRA C:2004, 20.8 - The signal handling facilities of <signal.h> shall not be used.
|
||||
* MISRA C:2012, 21.5 - The standard header file <signal.h> shall not be used
|
||||
|
||||
|
||||
ifdef::env-github,rspecator-view[]
|
||||
'''
|
||||
== Comments And Links
|
||||
(visible only on this page)
|
||||
|
||||
include::comments-and-links.adoc[]
|
||||
endif::env-github,rspecator-view[]
|
@ -0,0 +1,2 @@
|
||||
{
|
||||
}
|
@ -6,14 +6,20 @@ import { buildSearchIndex, buildIndexStore, DESCRIPTION_SPLIT_REGEX } from '../s
|
||||
|
||||
describe('index store generation', () => {
|
||||
test('merges rules metadata', () => {
|
||||
const rulesPath = path.join(__dirname, 'resources', 'plugin_rules');
|
||||
const rulesPath = path.join(__dirname, 'resources', 'metadata');
|
||||
const [indexStore, _] = buildIndexStore(rulesPath);
|
||||
const ruleS3457 = indexStore['S3457'];
|
||||
|
||||
expect(ruleS3457).toMatchObject({
|
||||
id: 'S3457',
|
||||
type: 'CODE_SMELL',
|
||||
languages: ['cfamily', 'csharp', 'java', 'python'],
|
||||
languages: [
|
||||
{ "name": "cfamily", "status": "ready", },
|
||||
{ "name": "csharp", "status": "ready", },
|
||||
{ "name": "default", "status": "ready", },
|
||||
{ "name": "java", "status": "closed", },
|
||||
{ "name": "python", "status": "deprecated", }
|
||||
],
|
||||
tags: ['cert', 'clumsy', 'confusing'],
|
||||
severities: ['Major', 'Minor'],
|
||||
qualityProfiles: ['MISRA C++ 2008 recommended', 'Sonar way'],
|
||||
@ -21,7 +27,7 @@ describe('index store generation', () => {
|
||||
});
|
||||
|
||||
test('stores description words', () => {
|
||||
const rulesPath = path.join(__dirname, 'resources', 'plugin_rules');
|
||||
const rulesPath = path.join(__dirname, 'resources', 'metadata');
|
||||
const [indexStore, _] = buildIndexStore(rulesPath);
|
||||
const ruleS3457 = indexStore['S3457'];
|
||||
|
||||
@ -34,28 +40,30 @@ describe('index store generation', () => {
|
||||
});
|
||||
|
||||
test('collects all tags', () => {
|
||||
const rulesPath = path.join(__dirname, 'resources', 'plugin_rules');
|
||||
const rulesPath = path.join(__dirname, 'resources', 'metadata');
|
||||
const [_, aggregates] = buildIndexStore(rulesPath);
|
||||
expect(aggregates.tags).toEqual({"based-on-misra": 1,
|
||||
"cert": 5,
|
||||
"clumsy": 4,
|
||||
"confusing": 4,
|
||||
"cert": 2,
|
||||
"clumsy": 2,
|
||||
"confusing": 1,
|
||||
"lock-in": 1,
|
||||
"misra-c++2008": 1,
|
||||
"pitfall": 1});
|
||||
"pitfall": 1
|
||||
});
|
||||
});
|
||||
|
||||
test('collects all languages', () => {
|
||||
const rulesPath = path.join(__dirname, 'resources', 'plugin_rules');
|
||||
const rulesPath = path.join(__dirname, 'resources', 'metadata');
|
||||
const [_, aggregates] = buildIndexStore(rulesPath);
|
||||
expect(aggregates.langs).toEqual({"cfamily": 3,
|
||||
"csharp": 1,
|
||||
"default": 3,
|
||||
"java": 1,
|
||||
"python": 1});
|
||||
});
|
||||
|
||||
test('collects all rule keys', () => {
|
||||
const rulesPath = path.join(__dirname, 'resources', 'plugin_rules');
|
||||
const rulesPath = path.join(__dirname, 'resources', 'metadata');
|
||||
const [indexStore, _] = buildIndexStore(rulesPath);
|
||||
expect(indexStore['S3457'].all_keys).toEqual(['RSPEC-3457', 'S3457']);
|
||||
expect(indexStore['S1000'].all_keys).toEqual(['RSPEC-1000', 'S1000', 'UnnamedNamespaceInHeader']);
|
||||
@ -63,7 +71,7 @@ describe('index store generation', () => {
|
||||
});
|
||||
|
||||
test('collects all quality profiles', () => {
|
||||
const rulesPath = path.join(__dirname, 'resources', 'plugin_rules');
|
||||
const rulesPath = path.join(__dirname, 'resources', 'metadata');
|
||||
const [_, aggregates] = buildIndexStore(rulesPath);
|
||||
expect(aggregates.qualityProfiles).toEqual({
|
||||
"MISRA C++ 2008 recommended": 2,
|
||||
@ -72,7 +80,7 @@ describe('index store generation', () => {
|
||||
});
|
||||
|
||||
function createIndex() {
|
||||
const rulesPath = path.join(__dirname, 'resources', 'plugin_rules');
|
||||
const rulesPath = path.join(__dirname, 'resources', 'metadata');
|
||||
const [indexStore, _] = buildIndexStore(rulesPath);
|
||||
|
||||
// Hack to avoid warnings when 'selectivePipeline' is already registered
|
||||
@ -141,7 +149,9 @@ describe('search index enables search by tags and quality profiles', () => {
|
||||
test('searches in rule tags', () => {
|
||||
const searchIndex = createIndex();
|
||||
const searchesS3457 = search(searchIndex, 'cert', 'tags');
|
||||
expect(searchesS3457).toEqual(['S1000', 'S3457']);
|
||||
expect(searchesS3457).toHaveLength(2);
|
||||
expect(searchesS3457).toContain('S1000');
|
||||
expect(searchesS3457).toContain('S3457');
|
||||
|
||||
const searchesS987 = search(searchIndex, 'based-on-misra', 'tags');
|
||||
expect(searchesS987).toEqual(['S987']);
|
||||
|
@ -2,7 +2,7 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
import asciidoctor from 'asciidoctor';
|
||||
|
||||
import { getRulesDirectories, listSupportedLanguage } from './utils';
|
||||
import { getRulesDirectories, listSupportedLanguages } from './utils';
|
||||
import { logger } from './deploymentLogger';
|
||||
|
||||
const asciidoc = asciidoctor();
|
||||
@ -30,17 +30,17 @@ asciidoc.LoggerManager.setLogger(winstonLogger);
|
||||
* @param srcDir directory containing the original rule metadata and description.
|
||||
* @param dstDir directory where the generated rules metadata and description will be written.
|
||||
*/
|
||||
export function generate_one_rule_description(srcDir: string, dstDir: string) {
|
||||
export function generateOneRuleDescription(srcDir: string, dstDir: string) {
|
||||
fs.mkdirSync(dstDir, { recursive: true });
|
||||
const all_languages = listSupportedLanguage(srcDir);
|
||||
const languages = listSupportedLanguages(srcDir);
|
||||
let default_descr_wanted = true;
|
||||
for (const language of all_languages) {
|
||||
const html = generate_rule_description(srcDir, language);
|
||||
const dstFile = path.join(dstDir, language + "-description.html");
|
||||
for (const language of languages) {
|
||||
const html = generateRuleDescription(srcDir, language);
|
||||
const dstFile = path.join(dstDir, language + '-description.html');
|
||||
fs.writeFileSync(dstFile, html, {encoding: 'utf8'});
|
||||
if (default_descr_wanted) {
|
||||
const dstFile = path.join(dstDir, "default-description.html");
|
||||
fs.writeFileSync(dstFile, html, {encoding: 'utf8'});
|
||||
const defFile = path.join(dstDir, 'default-description.html');
|
||||
fs.writeFileSync(defFile, html, {encoding: 'utf8'});
|
||||
default_descr_wanted = false;
|
||||
}
|
||||
}
|
||||
@ -52,9 +52,9 @@ export function generate_one_rule_description(srcDir: string, dstDir: string) {
|
||||
* @param dstPath directory where the generated rules metadata and description will be written.
|
||||
* @param rules an optional list of rules to list. Other rules won't be generated.
|
||||
*/
|
||||
export function generate_rules_description(srcPath: string, dstPath: string, rules?: string[]) {
|
||||
export function generateRulesDescription(srcPath: string, dstPath: string, rules?: string[]) {
|
||||
for (const { srcDir, dstDir } of getRulesDirectories(srcPath, dstPath, rules)) {
|
||||
generate_one_rule_description(srcDir, dstDir);
|
||||
generateOneRuleDescription(srcDir, dstDir);
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,12 +63,12 @@ export function generate_rules_description(srcPath: string, dstPath: string, rul
|
||||
* @param srcDir rule's source directory.
|
||||
* @param language language for which the metadata should be generated
|
||||
*/
|
||||
function generate_rule_description(srcDir: string, language: string) {
|
||||
let ruleSrcFile = path.join(srcDir, language, "rule.adoc");
|
||||
function generateRuleDescription(srcDir: string, language: string) {
|
||||
let ruleSrcFile = path.join(srcDir, language, 'rule.adoc');
|
||||
if (!fs.existsSync(ruleSrcFile)) {
|
||||
ruleSrcFile = path.join(srcDir, "rule.adoc");
|
||||
ruleSrcFile = path.join(srcDir, 'rule.adoc');
|
||||
if (!fs.existsSync(ruleSrcFile)) {
|
||||
throw new Error("Missing file 'rule.adoc' for language '" + language + " in " + srcDir);
|
||||
throw new Error(`Missing file 'rule.adoc' for language ${language} in ${srcDir}`);
|
||||
}
|
||||
}
|
||||
const baseDir = path.resolve(path.dirname(ruleSrcFile));
|
||||
|
@ -5,8 +5,8 @@ import yargs from 'yargs/yargs';
|
||||
import path from 'path';
|
||||
|
||||
import { clean_rules } from './clean';
|
||||
import { generate_one_rule_metadata, generate_rules_metadata } from './metadata';
|
||||
import { generate_one_rule_description, generate_rules_description, } from './description';
|
||||
import { generateOneRuleMetadata, generateRulesMetadata } from './metadata';
|
||||
import { generateOneRuleDescription, generateRulesDescription, } from './description';
|
||||
import { createIndexFiles } from './searchIndex';
|
||||
import { process_incomplete_rspecs, PullRequest } from './pullRequestIndexing';
|
||||
|
||||
@ -24,10 +24,10 @@ yargs(process.argv.slice(2))
|
||||
yargs.array<string>('rules')
|
||||
},
|
||||
(argv: any) => {
|
||||
generate_rules_metadata(RULE_SRC_DIRECTORY, RULE_DST_DIRECTORY, argv.rules)
|
||||
generateRulesMetadata(RULE_SRC_DIRECTORY, RULE_DST_DIRECTORY, argv.rules);
|
||||
process_incomplete_rspecs(PR_DIRECTORY, function (srcDir: string, pr: PullRequest) {
|
||||
const dstDir = path.join(RULE_DST_DIRECTORY, pr.rspec_id);
|
||||
generate_one_rule_metadata(srcDir, dstDir, pr.branch, pr.url);
|
||||
generateOneRuleMetadata(srcDir, dstDir, pr.branch, pr.url);
|
||||
})
|
||||
})
|
||||
|
||||
@ -36,10 +36,10 @@ yargs(process.argv.slice(2))
|
||||
yargs.array<string>('rules')
|
||||
},
|
||||
(argv: any) => {
|
||||
generate_rules_description(RULE_SRC_DIRECTORY, RULE_DST_DIRECTORY, argv.rules)
|
||||
generateRulesDescription(RULE_SRC_DIRECTORY, RULE_DST_DIRECTORY, argv.rules);
|
||||
process_incomplete_rspecs(PR_DIRECTORY, function (srcDir: string, pr: PullRequest) {
|
||||
const dstDir = path.join(RULE_DST_DIRECTORY, pr.rspec_id);
|
||||
generate_one_rule_description(srcDir, dstDir);
|
||||
generateOneRuleDescription(srcDir, dstDir);
|
||||
})
|
||||
})
|
||||
|
||||
@ -50,12 +50,12 @@ yargs(process.argv.slice(2))
|
||||
.command('*', 'generate rules metadata, description and index',
|
||||
() => {},
|
||||
(argv: any) => {
|
||||
generate_rules_metadata(RULE_SRC_DIRECTORY, RULE_DST_DIRECTORY);
|
||||
generate_rules_description(RULE_SRC_DIRECTORY, RULE_DST_DIRECTORY);
|
||||
generateRulesMetadata(RULE_SRC_DIRECTORY, RULE_DST_DIRECTORY);
|
||||
generateRulesDescription(RULE_SRC_DIRECTORY, RULE_DST_DIRECTORY);
|
||||
process_incomplete_rspecs(PR_DIRECTORY, function (srcDir: string, pr: PullRequest) {
|
||||
const dstDir = path.join(RULE_DST_DIRECTORY, pr.rspec_id);
|
||||
generate_one_rule_metadata(srcDir, dstDir, pr.branch, pr.url);
|
||||
generate_one_rule_description(srcDir, dstDir);
|
||||
generateOneRuleMetadata(srcDir, dstDir, pr.branch, pr.url);
|
||||
generateOneRuleDescription(srcDir, dstDir);
|
||||
}).then(function() {
|
||||
createIndexFiles(RULE_DST_DIRECTORY);
|
||||
});
|
||||
|
@ -1,7 +1,8 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { LanguageSupport } from '../types/RuleMetadata';
|
||||
|
||||
import { getRulesDirectories, listSupportedLanguage } from './utils';
|
||||
import { getRulesDirectories, listSupportedLanguages } from './utils';
|
||||
|
||||
/**
|
||||
* Generate rule metadata (for all relevant languages) and write it in the destination directory.
|
||||
@ -10,15 +11,19 @@ import { getRulesDirectories, listSupportedLanguage } from './utils';
|
||||
* @param branch the branch containing the given version of the rule. Typically 'master' but can be different for not merged rules.
|
||||
* @param prUrl optional link to the PR adding the rule. absent for merged rules.
|
||||
*/
|
||||
export function generate_one_rule_metadata(srcDir: string, dstDir: string,
|
||||
export function generateOneRuleMetadata(srcDir: string, dstDir: string,
|
||||
branch: string, prUrl?: string) {
|
||||
fs.mkdirSync(dstDir, { recursive: true });
|
||||
const allLanguages = listSupportedLanguage(srcDir);
|
||||
const allLanguages = listSupportedLanguages(srcDir);
|
||||
const allMetadata = allLanguages.map((language) => {
|
||||
const metadata = generate_rule_metadata(srcDir, language, allLanguages);
|
||||
const metadata = generateRuleMetadata(srcDir, language);
|
||||
return {language, metadata};
|
||||
});
|
||||
|
||||
// Update language status for all
|
||||
const languageSupports =
|
||||
allMetadata.map(m => ({name: m.language, status: m.metadata.status} as LanguageSupport));
|
||||
|
||||
// Merge all sqKeys in an array so that we can use it later to check rule coverage.
|
||||
const allKeys = allMetadata
|
||||
.reduce((set, {metadata}) => {
|
||||
@ -33,14 +38,15 @@ export function generate_one_rule_metadata(srcDir: string, dstDir: string,
|
||||
metadata.prUrl = prUrl;
|
||||
}
|
||||
metadata.branch = branch;
|
||||
metadata.languagesSupport = languageSupports;
|
||||
});
|
||||
|
||||
let default_metadata_wanted = true;
|
||||
for (const { language, metadata } of allMetadata) {
|
||||
const dstJsonFile = path.join(dstDir, language + "-metadata.json");
|
||||
const dstJsonFile = path.join(dstDir, language + '-metadata.json');
|
||||
fs.writeFileSync(dstJsonFile, JSON.stringify(metadata, null, 2), { encoding: 'utf8' })
|
||||
if (default_metadata_wanted) {
|
||||
const dstFile = path.join(dstDir, "default-metadata.json");
|
||||
const dstFile = path.join(dstDir, 'default-metadata.json');
|
||||
fs.writeFileSync(dstFile, JSON.stringify(metadata, null, 2), { encoding: 'utf8' });
|
||||
default_metadata_wanted = false;
|
||||
}
|
||||
@ -53,9 +59,9 @@ export function generate_one_rule_metadata(srcDir: string, dstDir: string,
|
||||
* @param dstPath directory where the generated rules metadata and description will be written.
|
||||
* @param rules an optional list of rules to list. Other rules won't be generated.
|
||||
*/
|
||||
export function generate_rules_metadata(srcPath: string, dstPath: string, rules?: string[]) {
|
||||
export function generateRulesMetadata(srcPath: string, dstPath: string, rules?: string[]) {
|
||||
for (const { srcDir, dstDir } of getRulesDirectories(srcPath, dstPath, rules)) {
|
||||
generate_one_rule_metadata(srcDir, dstDir, 'master');
|
||||
generateOneRuleMetadata(srcDir, dstDir, 'master');
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,14 +69,11 @@ export function generate_rules_metadata(srcPath: string, dstPath: string, rules?
|
||||
* Generate the metadata corresponding to one rule and one language.
|
||||
* @param srcDir rule's source directory.
|
||||
* @param language language for which the metadata should be generated
|
||||
* @param all_languages every language the rule supports.
|
||||
*/
|
||||
function generate_rule_metadata(srcDir: string, language: string, all_languages: string[]) {
|
||||
let parentFile = path.join(srcDir, language, "metadata.json");
|
||||
function generateRuleMetadata(srcDir: string, language: string) {
|
||||
const parentFile = path.join(srcDir, language, 'metadata.json');
|
||||
const parentJson = fs.existsSync(parentFile) ? JSON.parse(fs.readFileSync(parentFile, 'utf8')) : {};
|
||||
let childFile = path.join(srcDir, "metadata.json");
|
||||
const childFile = path.join(srcDir, 'metadata.json');
|
||||
const childJson = fs.existsSync(childFile) ? JSON.parse(fs.readFileSync(childFile, 'utf8')) : {};
|
||||
const mergedJson = {...childJson, ...parentJson};
|
||||
mergedJson["all_languages"] = all_languages;
|
||||
return mergedJson;
|
||||
return {...childJson, ...parentJson};
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import lunr, { Token } from 'lunr';
|
||||
|
||||
import { IndexedRule, IndexStore, Severity, IndexAggregates } from '../types/IndexStore';
|
||||
import { logger as rootLogger } from './deploymentLogger';
|
||||
import { LanguageSupport } from '../types/RuleMetadata';
|
||||
|
||||
const logger = rootLogger.child({ source: path.basename(__filename) })
|
||||
|
||||
@ -17,33 +18,16 @@ export interface IndexedRuleWithDescription extends IndexedRule {
|
||||
descriptions?: Array<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the index store. This store is indexed by lunr and later used by the frontend.
|
||||
* Whenever the lunr index finds something it returns IDs. The frontend will look at
|
||||
* metadata corresponding to this ID in the index store.
|
||||
* @param rulesPath Path to the directory containing aggregated metadata and rules
|
||||
* descriptions in HTML format.
|
||||
*/
|
||||
export function buildIndexStore(rulesPath: string):[Record<string,IndexedRuleWithDescription>, IndexAggregates] {
|
||||
let ruleDirs = fs.readdirSync(rulesPath).filter((fileName) => {
|
||||
const fullpath = path.join(rulesPath, fileName);
|
||||
return fs.lstatSync(fullpath).isDirectory();
|
||||
});
|
||||
let allTags: { [id: string]: number } = {};
|
||||
let allLangs: { [id: string]: number } = {};
|
||||
let allQualityProfiles: { [id: string]: number } = {};
|
||||
const indexedRecords = ruleDirs.map<[string, IndexedRuleWithDescription] | null>((ruleDir) => {
|
||||
const allLanguages = fs.readdirSync(path.join(rulesPath, ruleDir))
|
||||
.filter((fileName) => fileName.endsWith('-metadata.json'))
|
||||
.map((fileName) => fileName.split('-')[0]);
|
||||
function buildOneRuleRecord(allLanguages: string[], rulesPath: string, ruleDir: string) {
|
||||
|
||||
let types = new Set<string>();
|
||||
let severities = new Set<Severity>();
|
||||
const all_keys = new Set<string>([ruleDir]);
|
||||
const allKeys = new Set<string>([ruleDir]);
|
||||
const titles = new Set<string>();
|
||||
const tags = new Set<string>();
|
||||
const qualityProfiles = new Set<string>();
|
||||
const descriptions = new Set<string>();
|
||||
const supportedLanguages : LanguageSupport[] = [];
|
||||
let prUrl = undefined;
|
||||
|
||||
allLanguages.forEach((lang) => {
|
||||
@ -63,11 +47,12 @@ export function buildIndexStore(rulesPath: string):[Record<string,IndexedRuleWit
|
||||
if (metadata.prUrl) {
|
||||
prUrl = metadata.prUrl;
|
||||
}
|
||||
all_keys.add(metadata.sqKey);
|
||||
all_keys.add(metadata.ruleSpecification);
|
||||
allKeys.add(metadata.sqKey);
|
||||
allKeys.add(metadata.ruleSpecification);
|
||||
titles.add(metadata.title);
|
||||
types.add(metadata.type);
|
||||
severities.add(metadata.defaultSeverity as Severity);
|
||||
supportedLanguages.push({name: lang, status: metadata.status});
|
||||
if (metadata.tags) {
|
||||
for (const tag of metadata.tags) {
|
||||
tags.add(tag);
|
||||
@ -78,60 +63,103 @@ export function buildIndexStore(rulesPath: string):[Record<string,IndexedRuleWit
|
||||
qualityProfiles.add(qualityProfile);
|
||||
}
|
||||
}
|
||||
if (lang in allLangs) {
|
||||
allLangs[lang] += 1;
|
||||
} else {
|
||||
allLangs[lang] = 1;
|
||||
}
|
||||
tags.forEach((tag) => {
|
||||
if (tag in allTags) {
|
||||
allTags[tag] += 1;
|
||||
} else {
|
||||
allTags[tag] = 1;
|
||||
}
|
||||
});
|
||||
});
|
||||
qualityProfiles.forEach((qualityProfile) => {
|
||||
if (qualityProfile in allQualityProfiles) {
|
||||
allQualityProfiles[qualityProfile] += 1;
|
||||
} else {
|
||||
allQualityProfiles[qualityProfile] = 1;
|
||||
return {
|
||||
types,
|
||||
severities,
|
||||
allKeys,
|
||||
titles,
|
||||
supportedLanguages,
|
||||
tags,
|
||||
qualityProfiles,
|
||||
descriptions,
|
||||
prUrl
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
function buildOneRuleIndexedRecord(rulesPath: string, ruleDir: string)
|
||||
: [string, IndexedRuleWithDescription] | null {
|
||||
|
||||
const allLanguages = fs.readdirSync(path.join(rulesPath, ruleDir))
|
||||
.filter((fileName) => fileName.endsWith('-metadata.json'))
|
||||
.map((fileName) => (fileName.split('-')[0]));
|
||||
|
||||
const record = buildOneRuleRecord(allLanguages, rulesPath, ruleDir);
|
||||
|
||||
if (allLanguages.length < 1) {
|
||||
logger.error(`No languages found for rule ${ruleDir}, at least 1 is required`);
|
||||
return null;
|
||||
}
|
||||
if (types.size !== 1) {
|
||||
logger.error(`${types.size} type(s) found for rule ${ruleDir}, 1 is required: ${JSON.stringify(types)}`);
|
||||
if (record.types.size !== 1) {
|
||||
logger.error(
|
||||
`${record.types.size} type(s) found for rule ${ruleDir}, 1 is required: ${JSON.stringify(record.types)}`);
|
||||
return null;
|
||||
}
|
||||
if (severities.size < 1) {
|
||||
if (record.severities.size < 1) {
|
||||
logger.error(`No severity found for rule ${ruleDir}, at least 1 is required`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const indexedRecord: IndexedRuleWithDescription = {
|
||||
id: ruleDir,
|
||||
languages: allLanguages.sort(),
|
||||
type: types.values().next().value,
|
||||
severities: Array.from(severities).sort(),
|
||||
all_keys: Array.from(all_keys).sort(),
|
||||
titles: Array.from(titles).sort(),
|
||||
tags: Array.from(tags).sort(),
|
||||
qualityProfiles: Array.from(qualityProfiles).sort(),
|
||||
descriptions: Array.from(descriptions).sort(),
|
||||
prUrl
|
||||
languages: Array.from(record.supportedLanguages).sort(),
|
||||
type: record.types.values().next().value,
|
||||
severities: Array.from(record.severities).sort(),
|
||||
all_keys: Array.from(record.allKeys).sort(),
|
||||
titles: Array.from(record.titles).sort(),
|
||||
tags: Array.from(record.tags).sort(),
|
||||
qualityProfiles: Array.from(record.qualityProfiles).sort(),
|
||||
descriptions: Array.from(record.descriptions).sort(),
|
||||
prUrl: record.prUrl
|
||||
}
|
||||
|
||||
return [ruleDir, indexedRecord];
|
||||
}
|
||||
|
||||
function buildIndexAggregates(indexedRecords: [string, IndexedRuleWithDescription][]): IndexAggregates {
|
||||
const aggregates: IndexAggregates = { langs: {}, tags: {}, qualityProfiles: {} };
|
||||
|
||||
indexedRecords.forEach(record => {
|
||||
record[1].qualityProfiles.forEach((qualityProfile) => {
|
||||
if (qualityProfile in aggregates.qualityProfiles) {
|
||||
aggregates.qualityProfiles[qualityProfile] += 1;
|
||||
} else {
|
||||
aggregates.qualityProfiles[qualityProfile] = 1;
|
||||
}
|
||||
});
|
||||
record[1].languages.forEach(lang => {
|
||||
if (lang.name in aggregates.langs) {
|
||||
aggregates.langs[lang.name] += 1;
|
||||
} else {
|
||||
aggregates.langs[lang.name] = 1;
|
||||
}
|
||||
});
|
||||
record[1].tags.forEach((tag) => {
|
||||
if (tag in aggregates.tags) {
|
||||
aggregates.tags[tag] += 1;
|
||||
} else {
|
||||
aggregates.tags[tag] = 1;
|
||||
}
|
||||
});
|
||||
});
|
||||
return aggregates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the index store. This store is indexed by lunr and later used by the frontend.
|
||||
* Whenever the lunr index finds something it returns IDs. The frontend will look at
|
||||
* metadata corresponding to this ID in the index store.
|
||||
* @param rulesPath Path to the directory containing aggregated metadata and rules
|
||||
* descriptions in HTML format.
|
||||
*/
|
||||
export function buildIndexStore(rulesPath: string):[Record<string,IndexedRuleWithDescription>, IndexAggregates] {
|
||||
const ruleDirs = fs.readdirSync(rulesPath).filter((fileName) => {
|
||||
const fullpath = path.join(rulesPath, fileName);
|
||||
return fs.lstatSync(fullpath).isDirectory();
|
||||
});
|
||||
const indexedRecords = ruleDirs.map((ruleDir) => buildOneRuleIndexedRecord(rulesPath, ruleDir));
|
||||
const filteredRecords = indexedRecords.filter((value) => value !== null) as [string, IndexedRuleWithDescription][];
|
||||
|
||||
return [Object.fromEntries(filteredRecords),
|
||||
{langs: allLangs, tags: allTags, qualityProfiles: allQualityProfiles}];
|
||||
return [Object.fromEntries(filteredRecords), buildIndexAggregates(filteredRecords)];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -154,7 +182,7 @@ export function buildSearchIndex(ruleIndexStore: IndexStore) {
|
||||
|
||||
lunr.Pipeline.registerFunction(selectivePipeline, 'selectivePipeline');
|
||||
|
||||
var ruleIndex = lunr(function () {
|
||||
return lunr(function () {
|
||||
// Set our own token processing pipeline
|
||||
this.pipeline.reset();
|
||||
this.pipeline.add(selectivePipeline);
|
||||
@ -175,8 +203,6 @@ export function buildSearchIndex(ruleIndexStore: IndexStore) {
|
||||
this.add(transformedRecord);
|
||||
}
|
||||
})
|
||||
|
||||
return ruleIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,7 +27,7 @@ export function getRulesDirectories(srcPath: string, dstPath: string, rules?: st
|
||||
* List every language for which a rule has a specialization, i.e. a sub-directory.
|
||||
* @param ruleDirectory the rule's source directory
|
||||
*/
|
||||
export function listSupportedLanguage(ruleDirectory: string) {
|
||||
export function listSupportedLanguages(ruleDirectory: string): string[] {
|
||||
return fs.readdirSync(ruleDirectory)
|
||||
.filter(fileName => fs.lstatSync(path.join(ruleDirectory, fileName)).isDirectory())
|
||||
.sort();
|
||||
|
@ -1,8 +1,10 @@
|
||||
import {LanguageSupport} from './RuleMetadata';
|
||||
|
||||
export type Severity = 'Blocker'|'Critical'|'Major'|'Minor'|'Info';
|
||||
|
||||
export interface IndexedRule {
|
||||
id: string;
|
||||
languages: string[];
|
||||
languages: LanguageSupport[];
|
||||
// FIXME: type, defaultSeverity should never be null but the index generation has a bug
|
||||
type: 'BUG'|'CODE_SMELL'|'VULNERABILITY'|'SECURITY_HOTSPOT';
|
||||
severities: Severity[];
|
||||
|
@ -1,6 +1,13 @@
|
||||
export type Status = 'ready' | 'beta' | 'closed' | 'deprecated' | 'superseded';
|
||||
|
||||
export interface LanguageSupport {
|
||||
name: string,
|
||||
status: Status
|
||||
}
|
||||
|
||||
export default interface RuleMetadata {
|
||||
title: string,
|
||||
all_languages: string[],
|
||||
languagesSupport: LanguageSupport[],
|
||||
allKeys: string[],
|
||||
branch: string,
|
||||
prUrl?: string
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { useFetch } from './useFetch';
|
||||
import { Status } from '../types/RuleMetadata';
|
||||
|
||||
type Version = string | { since: string, until: string };
|
||||
type RuleCoverage = Record<string, Record<string, Version>>;
|
||||
@ -77,7 +78,28 @@ export function useRuleCoverage() {
|
||||
return ruleCoverageForSonarpediaKeys(allLanguageKeys, ruleKeys, mapper);
|
||||
}
|
||||
|
||||
function ruleStateInAnalyzer(language: string, ruleKeys: string[]): 'covered' | 'targeted' | 'removed' {
|
||||
type AnalyzerState = 'covered' | 'targeted' | 'removed' | 'closed' | 'deprecated';
|
||||
function analyzerStateFromCoverageAndStatus(coverage: Version[], status: Status): AnalyzerState {
|
||||
if (coverage.length > 0) {
|
||||
if (coverage.some(version => typeof version === 'string')) {
|
||||
// if there is at least one coverage with simple (string) type, rule is still part of analyzer
|
||||
if (status === 'deprecated' || status === 'superseded') {
|
||||
return 'deprecated';
|
||||
} else {
|
||||
return 'covered';
|
||||
}
|
||||
} else {
|
||||
// all coverages keep an analyzer versions range which means the rule was removed
|
||||
return 'removed';
|
||||
}
|
||||
} else if (status === 'closed') {
|
||||
return 'closed';
|
||||
} else {
|
||||
return 'targeted';
|
||||
}
|
||||
}
|
||||
|
||||
function ruleStateInAnalyzer(language: string, ruleKeys: string[], status: Status): AnalyzerState {
|
||||
const languageKeys = languageToSonarpedia.get(language);
|
||||
if (!languageKeys || coveredRulesError || coveredRulesIsLoading) {
|
||||
if (coveredRulesError) {
|
||||
@ -98,15 +120,7 @@ export function useRuleCoverage() {
|
||||
})
|
||||
);
|
||||
|
||||
if (result.length > 0) {
|
||||
// if there is at least one entry with simple (string) type, rule is still part of analyzer
|
||||
// otherwise (when all entries keep an analyzer versions range) the rule is removed
|
||||
return result.some(version => typeof version === 'string')
|
||||
? 'covered'
|
||||
: 'removed';
|
||||
} else {
|
||||
return 'targeted';
|
||||
}
|
||||
return analyzerStateFromCoverageAndStatus(result, status);
|
||||
}
|
||||
|
||||
return {ruleCoverage, allLangsRuleCoverage, ruleStateInAnalyzer};
|
||||
@ -119,13 +133,23 @@ export const RULE_STATE = {
|
||||
'darker': '#25699d'
|
||||
},
|
||||
'targeted': {
|
||||
// orange
|
||||
'color': '#FD7D20',
|
||||
'darker': '#E26003'
|
||||
// blueish green
|
||||
'color': '#8aa8a6',
|
||||
'darker': '#6c9390'
|
||||
},
|
||||
'removed': {
|
||||
// red
|
||||
'color': '#C72B28',
|
||||
'darker': '#8D1B19'
|
||||
},
|
||||
'deprecated' : {
|
||||
// orange
|
||||
'color': '#FD7D20',
|
||||
'darker': '#E26003'
|
||||
},
|
||||
'closed' : {
|
||||
// dark grey
|
||||
'color': '#505050',
|
||||
'darker': '#202020'
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user