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.test.inclusions=**/*.test.js,**/*.test.jsx,**/*.test.ts,**/*.test.tsx
|
||||||
sonar.javascript.coveragePlugin=lcov
|
sonar.javascript.coveragePlugin=lcov
|
||||||
sonar.javascript.lcov.reportPaths=coverage/lcov.info
|
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,
|
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({
|
const languageToJiraProject = new Map(Object.entries({
|
||||||
"PYTHON": "SONARPY",
|
'PYTHON': 'SONARPY',
|
||||||
"ABAP": "SONARABAP",
|
'ABAP': 'SONARABAP',
|
||||||
"CFAMILY": "CPP",
|
'CFAMILY': 'CPP',
|
||||||
"JAVA": "SONARJAVA",
|
'JAVA': 'SONARJAVA',
|
||||||
"COBOL": "SONARCOBOL",
|
'COBOL': 'SONARCOBOL',
|
||||||
"FLEX": "SONARFLEX",
|
'FLEX': 'SONARFLEX',
|
||||||
"HTML": "SONARHTML",
|
'HTML': 'SONARHTML',
|
||||||
"PHP": "SONARPHP",
|
'PHP': 'SONARPHP',
|
||||||
"PLI": "SONARPLI",
|
'PLI': 'SONARPLI',
|
||||||
"PLSQL": "SONARPLSQL",
|
'PLSQL': 'SONARPLSQL',
|
||||||
"RPG": "SONARRPG",
|
'RPG': 'SONARRPG',
|
||||||
"APEX": "SONARSLANG",
|
'APEX': 'SONARSLANG',
|
||||||
"RUBY": "SONARSLANG",
|
'RUBY': 'SONARSLANG',
|
||||||
"KOTLIN": "SONARKT",
|
'KOTLIN': 'SONARKT',
|
||||||
"SCALA": "SONARSLANG",
|
'SCALA': 'SONARSLANG',
|
||||||
"GO": "SONARSLANG",
|
'GO': 'SONARSLANG',
|
||||||
"SECRETS": "SECRETS",
|
'SECRETS': 'SECRETS',
|
||||||
"SWIFT": "SONARSWIFT",
|
'SWIFT': 'SONARSWIFT',
|
||||||
"TSQL": "SONARTSQL",
|
'TSQL': 'SONARTSQL',
|
||||||
"VB6": "SONARVBSIX",
|
'VB6': 'SONARVBSIX',
|
||||||
"XML": "SONARXML",
|
'XML': 'SONARXML',
|
||||||
"CLOUDFORMATION": "SONARIAC",
|
'CLOUDFORMATION': 'SONARIAC',
|
||||||
"TERRAFORM": "SONARIAC",
|
'TERRAFORM': 'SONARIAC',
|
||||||
"TEXT": "SONARTEXT",
|
'TEXT': 'SONARTEXT',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const languageToGithubProject = new Map(Object.entries({
|
const languageToGithubProject = new Map(Object.entries({
|
||||||
"ABAP": "sonar-abap",
|
'ABAP': 'sonar-abap',
|
||||||
"CSHARP": "sonar-dotnet",
|
'CSHARP': 'sonar-dotnet',
|
||||||
"VBNET": "sonar-dotnet",
|
'VBNET': 'sonar-dotnet',
|
||||||
"JAVASCRIPT": "SonarJS",
|
'JAVASCRIPT': 'SonarJS',
|
||||||
"TYPESCRIPT": "SonarJS",
|
'TYPESCRIPT': 'SonarJS',
|
||||||
"SWIFT": "sonar-swift",
|
'SWIFT': 'sonar-swift',
|
||||||
"KOTLIN": "sonar-kotlin",
|
'KOTLIN': 'sonar-kotlin',
|
||||||
"GO": "slang-enterprise",
|
'GO': 'slang-enterprise',
|
||||||
"SCALA": "slang-enterprise",
|
'SCALA': 'slang-enterprise',
|
||||||
"RUBY": "slang-enterprise",
|
'RUBY': 'slang-enterprise',
|
||||||
"APEX": "slang-enterprise",
|
'APEX': 'slang-enterprise',
|
||||||
"HTML": "sonar-html",
|
'HTML': 'sonar-html',
|
||||||
"COBOL": "sonar-cobol",
|
'COBOL': 'sonar-cobol',
|
||||||
"VB6": "sonar-vb",
|
'VB6': 'sonar-vb',
|
||||||
"JAVA": "sonar-java",
|
'JAVA': 'sonar-java',
|
||||||
"PLI": "sonar-pli",
|
'PLI': 'sonar-pli',
|
||||||
"CFAMILY": "sonar-cpp",
|
'CFAMILY': 'sonar-cpp',
|
||||||
"CSS": "sonar-css",
|
'CSS': 'sonar-css',
|
||||||
"FLEX": "sonar-flex",
|
'FLEX': 'sonar-flex',
|
||||||
"PHP": "sonar-php",
|
'PHP': 'sonar-php',
|
||||||
"PLSQL": "sonar-plsql",
|
'PLSQL': 'sonar-plsql',
|
||||||
"PYTHON": "sonar-python",
|
'PYTHON': 'sonar-python',
|
||||||
"RPG": "sonar-rpg",
|
'RPG': 'sonar-rpg',
|
||||||
"TSQL": "sonar-tsql",
|
'TSQL': 'sonar-tsql',
|
||||||
"XML": "sonar-xml",
|
'XML': 'sonar-xml',
|
||||||
"CLOUDFORMATION": "sonar-iac",
|
'CLOUDFORMATION': 'sonar-iac',
|
||||||
"TERRAFORM": "sonar-iac",
|
'TERRAFORM': 'sonar-iac',
|
||||||
"SECRETS": "sonar-secrets",
|
'SECRETS': 'sonar-secrets',
|
||||||
"TEXT": "sonar-text",
|
'TEXT': 'sonar-text',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function ticketsAndImplementationPRsLinks(ruleNumber: string, title: string, language?: string) {
|
function ticketsAndImplementationPRsLinks(ruleNumber: string, title: string, language?: string) {
|
||||||
@ -193,16 +203,16 @@ export function RulePage(props: any) {
|
|||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
let branch = 'master'
|
let branch = 'master'
|
||||||
|
|
||||||
let descUrl = process.env.PUBLIC_URL + '/rules/' + ruleid + "/" + (language ?? "default") + "-description.html";
|
const descUrl = `${process.env.PUBLIC_URL}/rules/${ruleid}/${language ?? 'default'}-description.html`;
|
||||||
let metadataUrl = process.env.PUBLIC_URL + '/rules/' + ruleid + "/" + (language ?? "default") + "-metadata.json";
|
const metadataUrl = `${process.env.PUBLIC_URL}/rules/${ruleid}/${language ?? 'default'}-metadata.json`;
|
||||||
|
|
||||||
let [descHTML, descError, descIsLoading] = useFetch<string>(descUrl, false);
|
let [descHTML, descError, descIsLoading] = useFetch<string>(descUrl, false);
|
||||||
let [metadataJSON, metadataError, metadataIsLoading] = useFetch<RuleMetadata>(metadataUrl);
|
let [metadataJSON, metadataError, metadataIsLoading] = useFetch<RuleMetadata>(metadataUrl);
|
||||||
|
|
||||||
const {ruleCoverage, allLangsRuleCoverage, ruleStateInAnalyzer} = useRuleCoverage();
|
const {ruleCoverage, allLangsRuleCoverage, ruleStateInAnalyzer} = useRuleCoverage();
|
||||||
let coverage: any = "Loading...";
|
let coverage: any = 'Loading...';
|
||||||
|
|
||||||
let title = "Loading..."
|
let title = 'Loading...';
|
||||||
let metadataJSONString;
|
let metadataJSONString;
|
||||||
let languagesTabs = null;
|
let languagesTabs = null;
|
||||||
let prUrl: string | undefined = undefined;
|
let prUrl: string | undefined = undefined;
|
||||||
@ -212,16 +222,16 @@ export function RulePage(props: any) {
|
|||||||
prUrl = metadataJSON.prUrl;
|
prUrl = metadataJSON.prUrl;
|
||||||
}
|
}
|
||||||
branch = metadataJSON.branch;
|
branch = metadataJSON.branch;
|
||||||
metadataJSON.all_languages.sort();
|
metadataJSON.languagesSupport.sort();
|
||||||
languagesTabs = metadataJSON.all_languages.map(lang => {
|
languagesTabs = metadataJSON.languagesSupport.map(({ name, status }) => {
|
||||||
const ruleState = ruleStateInAnalyzer(lang, metadataJSON!.allKeys);
|
const ruleState = ruleStateInAnalyzer(name, metadataJSON!.allKeys, status);
|
||||||
const classNames = classes.tab + ' ' + (classes as any)[ruleState + 'Tab'];
|
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);
|
metadataJSONString = JSON.stringify(metadataJSON, null, 2);
|
||||||
|
|
||||||
const coverageMapper = (key: any, range: any) => {
|
const coverageMapper = (key: any, range: any) => {
|
||||||
if (typeof range === "string") {
|
if (typeof range === 'string') {
|
||||||
return (
|
return (
|
||||||
<li key={key} >{key}: {range}</li>
|
<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;
|
prUrl = undefined;
|
||||||
branch = 'master';
|
branch = 'master';
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,22 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
backgroundColor: RULE_STATE['removed'].darker
|
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: {
|
targetedMarker: {
|
||||||
marginTop: theme.spacing(2),
|
marginTop: theme.spacing(2),
|
||||||
marginRight: theme.spacing(2),
|
marginRight: theme.spacing(2),
|
||||||
@ -65,6 +81,18 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
marginRight: theme.spacing(2),
|
marginRight: theme.spacing(2),
|
||||||
borderColor: RULE_STATE['removed'].color,
|
borderColor: RULE_STATE['removed'].color,
|
||||||
color: 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 coveredLanguages: JSX.Element[] = [];
|
||||||
const targetedLanguages: JSX.Element[] = [];
|
const targetedLanguages: JSX.Element[] = [];
|
||||||
const removedLanguages: 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 => {
|
actualLanguages.forEach(lang => {
|
||||||
const ruleState = ruleStateInAnalyzer(lang, props.data.all_keys);
|
const ruleState = ruleStateInAnalyzer(lang.name, props.data.all_keys, lang.status);
|
||||||
const chip = <Link key={lang} component={RouterLink} to={`/${props.data.id}/${lang}`}
|
const chip = <Link key={lang.name} component={RouterLink} to={`/${props.data.id}/${lang.name}`}
|
||||||
style={{ textDecoration: 'none' }}>
|
style={{ textDecoration: 'none' }}>
|
||||||
<Chip
|
<Chip
|
||||||
classes={{root: (classes as any)[ruleState + 'LanguageChip']}}
|
classes={{root: (classes as any)[ruleState + 'LanguageChip']}}
|
||||||
label={lang}
|
label={lang.name}
|
||||||
color="primary"
|
color="primary"
|
||||||
clickable
|
clickable
|
||||||
key="{lang}"
|
key="{lang}"
|
||||||
/>
|
/>
|
||||||
</Link>;
|
</Link>;
|
||||||
if (ruleState === 'covered') {
|
switch(ruleState) {
|
||||||
coveredLanguages.push(chip);
|
case 'targeted':
|
||||||
} else if (ruleState === 'targeted') {
|
|
||||||
targetedLanguages.push(chip);
|
targetedLanguages.push(chip);
|
||||||
} else {
|
break;
|
||||||
|
case 'removed':
|
||||||
removedLanguages.push(chip);
|
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 => (
|
const titles = props.data.titles.map(title => (
|
||||||
@ -125,6 +166,17 @@ export function SearchHit(props: SearchHitProps) {
|
|||||||
{removedLanguages}
|
{removedLanguages}
|
||||||
</Typography>;
|
</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 (
|
return (
|
||||||
<Card variant="outlined" classes={{root: classes.searchHit}}>
|
<Card variant="outlined" classes={{root: classes.searchHit}}>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
@ -137,6 +189,8 @@ export function SearchHit(props: SearchHitProps) {
|
|||||||
{coveredBlock}
|
{coveredBlock}
|
||||||
{targetedBlock}
|
{targetedBlock}
|
||||||
{removedBlock}
|
{removedBlock}
|
||||||
|
{deprecatedBlock}
|
||||||
|
{closedBlock}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
|
@ -7,7 +7,7 @@ import { Router } from 'react-router-dom';
|
|||||||
import { createMemoryHistory } from 'history';
|
import { createMemoryHistory } from 'history';
|
||||||
import { fetchMock } from '../testutils'
|
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) {
|
function readRuleFile(ruleId, filename) {
|
||||||
return fs.readFileSync(path.join(rulesPath, ruleId, filename)).toString();
|
return fs.readFileSync(path.join(rulesPath, ruleId, filename)).toString();
|
||||||
|
@ -17,7 +17,7 @@ function normalize(obj) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
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 [indexStore, indexAggregates] = buildIndexStore(rulePath);
|
||||||
const searchIndex = buildSearchIndex(indexStore);
|
const searchIndex = buildSearchIndex(indexStore);
|
||||||
const rootUrl = process.env.PUBLIC_URL;
|
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-store.json`] = {json: normalize(indexStore)};
|
||||||
mockUrls[`${rootUrl}/rules/rule-index-aggregates.json`] = {json: normalize(indexAggregates)};
|
mockUrls[`${rootUrl}/rules/rule-index-aggregates.json`] = {json: normalize(indexAggregates)};
|
||||||
mockUrls[`${rootUrl}/covered_rules.json`] = {json:
|
mockUrls[`${rootUrl}/covered_rules.json`] = {json:
|
||||||
{'ABAP': {'S100': 'ver1', 'S200': 'ver2'},
|
{'CPP': {'S1000': 'ver1', 'S987': 'ver2', 'S3457': 'ver1'},
|
||||||
'C': {'S100': 'c1', 'S234': {'since': 'c2', 'until': 'c3'}}}
|
'C': {'S1000': 'c1', 'S234': {'since': 'c2', 'until': 'c3'}},
|
||||||
|
'PY': {'S3457': {'since': 'p2', 'until': 'p3'}}}
|
||||||
};
|
};
|
||||||
jest.spyOn(global, 'fetch').mockImplementation(fetchMock(mockUrls));
|
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"
|
class="MuiTypography-root makeStyles-language-16 MuiTypography-body2"
|
||||||
>
|
>
|
||||||
<div
|
<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
|
<span
|
||||||
class="MuiChip-label"
|
class="MuiChip-label"
|
||||||
>
|
>
|
||||||
Targeted
|
Covered
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
@ -355,7 +355,7 @@ exports[`renders the list of all rules 1`] = `
|
|||||||
style="text-decoration: none;"
|
style="text-decoration: none;"
|
||||||
>
|
>
|
||||||
<div
|
<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"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
@ -374,7 +374,7 @@ exports[`renders the list of all rules 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="MuiBox-root MuiBox-root-23 makeStyles-searchHitBox-2"
|
class="MuiBox-root MuiBox-root-27 makeStyles-searchHitBox-2"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="MuiPaper-root MuiCard-root makeStyles-searchHit-14 MuiPaper-outlined MuiPaper-rounded"
|
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"
|
class="MuiTypography-root makeStyles-language-16 MuiTypography-body2"
|
||||||
>
|
>
|
||||||
<div
|
<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
|
<span
|
||||||
class="MuiChip-label"
|
class="MuiChip-label"
|
||||||
>
|
>
|
||||||
Targeted
|
Covered
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
@ -427,7 +427,7 @@ exports[`renders the list of all rules 1`] = `
|
|||||||
style="text-decoration: none;"
|
style="text-decoration: none;"
|
||||||
>
|
>
|
||||||
<div
|
<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"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
@ -441,6 +441,19 @@ exports[`renders the list of all rules 1`] = `
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</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
|
<a
|
||||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorPrimary"
|
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorPrimary"
|
||||||
href="/S3457/csharp"
|
href="/S3457/csharp"
|
||||||
@ -461,33 +474,26 @@ exports[`renders the list of all rules 1`] = `
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<a
|
</div>
|
||||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorPrimary"
|
<div
|
||||||
href="/S3457/java"
|
class="MuiTypography-root makeStyles-language-16 MuiTypography-body2"
|
||||||
style="text-decoration: none;"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="MuiButtonBase-root MuiChip-root makeStyles-targetedLanguageChip-18 MuiChip-colorPrimary MuiChip-clickableColorPrimary MuiChip-clickable"
|
class="MuiChip-root makeStyles-removedMarker-24 MuiChip-colorSecondary MuiChip-outlined MuiChip-outlinedSecondary"
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="MuiChip-label"
|
class="MuiChip-label"
|
||||||
>
|
>
|
||||||
java
|
Removed
|
||||||
</span>
|
</span>
|
||||||
<span
|
|
||||||
class="MuiTouchRipple-root"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</a>
|
|
||||||
<a
|
<a
|
||||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorPrimary"
|
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorPrimary"
|
||||||
href="/S3457/python"
|
href="/S3457/python"
|
||||||
style="text-decoration: none;"
|
style="text-decoration: none;"
|
||||||
>
|
>
|
||||||
<div
|
<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"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
@ -502,11 +508,44 @@ exports[`renders the list of all rules 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="MuiBox-root MuiBox-root-24 makeStyles-searchHitBox-2"
|
class="MuiBox-root MuiBox-root-28 makeStyles-searchHitBox-2"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="MuiPaper-root MuiCard-root makeStyles-searchHit-14 MuiPaper-outlined MuiPaper-rounded"
|
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"
|
class="MuiTypography-root makeStyles-language-16 MuiTypography-body2"
|
||||||
>
|
>
|
||||||
<div
|
<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
|
<span
|
||||||
class="MuiChip-label"
|
class="MuiChip-label"
|
||||||
>
|
>
|
||||||
Targeted
|
Covered
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
@ -549,7 +588,7 @@ exports[`renders the list of all rules 1`] = `
|
|||||||
style="text-decoration: none;"
|
style="text-decoration: none;"
|
||||||
>
|
>
|
||||||
<div
|
<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"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import { generate_rules_description } from '../description';
|
import { generateRulesDescription } from '../description';
|
||||||
import { withTestDir, createFiles } from '../testutils';
|
import { withTestDir, createFiles } from '../testutils';
|
||||||
|
|
||||||
describe('description generation', () => {
|
describe('description generation', () => {
|
||||||
@ -14,7 +14,7 @@ test('generates html from asciidoc', () => {
|
|||||||
'Specific content'].join('\n')
|
'Specific content'].join('\n')
|
||||||
});
|
});
|
||||||
return withTestDir(async (dstPath) => {
|
return withTestDir(async (dstPath) => {
|
||||||
generate_rules_description(srcPath, dstPath);
|
generateRulesDescription(srcPath, dstPath);
|
||||||
|
|
||||||
const ruleHtml = fs.readFileSync(path.join(dstPath, 'S100', 'java-description.html'));
|
const ruleHtml = fs.readFileSync(path.join(dstPath, 'S100', 'java-description.html'));
|
||||||
expect(ruleHtml.toString()).toEqual(
|
expect(ruleHtml.toString()).toEqual(
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import { generate_one_rule_metadata, generate_rules_metadata } from '../metadata';
|
import { generateOneRuleMetadata, generateRulesMetadata } from '../metadata';
|
||||||
import { withTestDir, createFiles } from '../testutils';
|
import { withTestDir, createFiles } from '../testutils';
|
||||||
|
|
||||||
describe('metadata generation', () => {
|
describe('metadata generation', () => {
|
||||||
@ -21,7 +21,7 @@ describe('metadata generation', () => {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
return withTestDir(async (dstPath) => {
|
return withTestDir(async (dstPath) => {
|
||||||
generate_rules_metadata(srcPath, dstPath);
|
generateRulesMetadata(srcPath, dstPath);
|
||||||
const javaStrMetadata = fs.readFileSync(`${dstPath}/S100/java-metadata.json`);
|
const javaStrMetadata = fs.readFileSync(`${dstPath}/S100/java-metadata.json`);
|
||||||
const javaMetadata = JSON.parse(javaStrMetadata.toString());
|
const javaMetadata = JSON.parse(javaStrMetadata.toString());
|
||||||
expect(javaMetadata).toMatchObject({
|
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', () => {
|
test('generates only requested rules if a list of rule is provided', () => {
|
||||||
return withTestDir((srcPath) => {
|
return withTestDir((srcPath) => {
|
||||||
createFiles(srcPath, {
|
createFiles(srcPath, {
|
||||||
@ -50,7 +83,7 @@ describe('metadata generation', () => {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
return withTestDir(async (dstPath) => {
|
return withTestDir(async (dstPath) => {
|
||||||
generate_rules_metadata(srcPath, dstPath, ['S100']);
|
generateRulesMetadata(srcPath, dstPath, ['S100']);
|
||||||
|
|
||||||
const s100Exists = fs.existsSync(`${dstPath}/S100/java-metadata.json`);
|
const s100Exists = fs.existsSync(`${dstPath}/S100/java-metadata.json`);
|
||||||
expect(s100Exists).toBeTruthy();
|
expect(s100Exists).toBeTruthy();
|
||||||
@ -72,7 +105,7 @@ describe('metadata generation', () => {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
return withTestDir(async (dstPath) => {
|
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 s100StrMetadata = fs.readFileSync(`${dstPath}/S100/java-metadata.json`);
|
||||||
const s100Metadata = JSON.parse(s100StrMetadata.toString());
|
const s100Metadata = JSON.parse(s100StrMetadata.toString());
|
||||||
@ -80,7 +113,7 @@ describe('metadata generation', () => {
|
|||||||
expect(s100Metadata.branch).toEqual('master');
|
expect(s100Metadata.branch).toEqual('master');
|
||||||
expect(Object.keys(s100Metadata)).not.toContain('prUrl');
|
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`);
|
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",
|
"title": "Header files should not contain unnamed namespaces",
|
||||||
"type": "CODE_SMELL",
|
"type": "CODE_SMELL",
|
||||||
"status": "ready",
|
"status": "ready",
|
||||||
|
"quickfix": "unknown",
|
||||||
"remediation": {
|
"remediation": {
|
||||||
"func": "Constant/Issue",
|
"func": "Constant/Issue",
|
||||||
"constantCost": "1h"
|
"constantCost": "1h"
|
||||||
@ -9,12 +10,12 @@
|
|||||||
"tags": [
|
"tags": [
|
||||||
"cert",
|
"cert",
|
||||||
"misra-c++2008",
|
"misra-c++2008",
|
||||||
|
"clumsy",
|
||||||
"pitfall"
|
"pitfall"
|
||||||
],
|
],
|
||||||
"extra": {
|
"extra": {
|
||||||
"coveredLanguages": [
|
"legacyKeys": [
|
||||||
"C++",
|
"UnnamedNamespaceInHeader"
|
||||||
"C"
|
|
||||||
],
|
],
|
||||||
"replacementRules": []
|
"replacementRules": []
|
||||||
},
|
},
|
||||||
@ -22,12 +23,20 @@
|
|||||||
"ruleSpecification": "RSPEC-1000",
|
"ruleSpecification": "RSPEC-1000",
|
||||||
"sqKey": "UnnamedNamespaceInHeader",
|
"sqKey": "UnnamedNamespaceInHeader",
|
||||||
"scope": "Main",
|
"scope": "Main",
|
||||||
|
"securityStandards": {
|
||||||
|
"CERT": [
|
||||||
|
"DCL59-CPP."
|
||||||
|
]
|
||||||
|
},
|
||||||
"defaultQualityProfiles": [
|
"defaultQualityProfiles": [
|
||||||
"Sonar way",
|
"Sonar way",
|
||||||
"MISRA C++ 2008 recommended"
|
"MISRA C++ 2008 recommended"
|
||||||
],
|
],
|
||||||
"all_languages": [
|
"languagesSupport": [
|
||||||
"cfamily"
|
{
|
||||||
|
"name": "cfamily",
|
||||||
|
"status": "ready"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"allKeys": [
|
"allKeys": [
|
||||||
"UnnamedNamespaceInHeader"
|
"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",
|
"title": "Printf-style format strings should be used correctly",
|
||||||
"type": "CODE_SMELL",
|
|
||||||
"status": "ready",
|
|
||||||
"remediation": {
|
|
||||||
"func": "Constant\/Issue",
|
|
||||||
"constantCost": "10min"
|
|
||||||
},
|
|
||||||
"tags": [
|
"tags": [
|
||||||
"cert",
|
"cert",
|
||||||
"confusing",
|
"confusing",
|
||||||
"clumsy"
|
"clumsy"
|
||||||
],
|
],
|
||||||
"defaultQualityProfiles": [
|
"defaultQualityProfiles": [
|
||||||
|
"Sonar way",
|
||||||
"MISRA C++ 2008 recommended"
|
"MISRA C++ 2008 recommended"
|
||||||
],
|
],
|
||||||
"defaultSeverity": "Minor",
|
"defaultSeverity": "Minor",
|
||||||
"ruleSpecification": "RSPEC-3457",
|
"securityStandards": {
|
||||||
"sqKey": "S3457",
|
"CERT": [
|
||||||
"scope": "All"
|
"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",
|
"ruleSpecification": "RSPEC-3457",
|
||||||
"sqKey": "S3457",
|
"sqKey": "S3457",
|
||||||
"scope": "All",
|
"scope": "All",
|
||||||
"all_languages": [
|
|
||||||
"cfamily",
|
|
||||||
"csharp",
|
|
||||||
"java",
|
|
||||||
"python"
|
|
||||||
],
|
|
||||||
"allKeys": [
|
"allKeys": [
|
||||||
"S3457"
|
"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",
|
"based-on-misra",
|
||||||
"lock-in"
|
"lock-in"
|
||||||
],
|
],
|
||||||
|
"extra": {
|
||||||
|
"replacementRules": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"legacyKeys": [
|
||||||
|
"PPIncludeSignal"
|
||||||
|
]
|
||||||
|
},
|
||||||
"defaultSeverity": "Critical",
|
"defaultSeverity": "Critical",
|
||||||
"ruleSpecification": "RSPEC-987",
|
"ruleSpecification": "RSPEC-987",
|
||||||
"sqKey": "PPIncludeSignal",
|
"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', () => {
|
describe('index store generation', () => {
|
||||||
test('merges rules metadata', () => {
|
test('merges rules metadata', () => {
|
||||||
const rulesPath = path.join(__dirname, 'resources', 'plugin_rules');
|
const rulesPath = path.join(__dirname, 'resources', 'metadata');
|
||||||
const [indexStore, _] = buildIndexStore(rulesPath);
|
const [indexStore, _] = buildIndexStore(rulesPath);
|
||||||
const ruleS3457 = indexStore['S3457'];
|
const ruleS3457 = indexStore['S3457'];
|
||||||
|
|
||||||
expect(ruleS3457).toMatchObject({
|
expect(ruleS3457).toMatchObject({
|
||||||
id: 'S3457',
|
id: 'S3457',
|
||||||
type: 'CODE_SMELL',
|
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'],
|
tags: ['cert', 'clumsy', 'confusing'],
|
||||||
severities: ['Major', 'Minor'],
|
severities: ['Major', 'Minor'],
|
||||||
qualityProfiles: ['MISRA C++ 2008 recommended', 'Sonar way'],
|
qualityProfiles: ['MISRA C++ 2008 recommended', 'Sonar way'],
|
||||||
@ -21,7 +27,7 @@ describe('index store generation', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('stores description words', () => {
|
test('stores description words', () => {
|
||||||
const rulesPath = path.join(__dirname, 'resources', 'plugin_rules');
|
const rulesPath = path.join(__dirname, 'resources', 'metadata');
|
||||||
const [indexStore, _] = buildIndexStore(rulesPath);
|
const [indexStore, _] = buildIndexStore(rulesPath);
|
||||||
const ruleS3457 = indexStore['S3457'];
|
const ruleS3457 = indexStore['S3457'];
|
||||||
|
|
||||||
@ -34,28 +40,30 @@ describe('index store generation', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('collects all tags', () => {
|
test('collects all tags', () => {
|
||||||
const rulesPath = path.join(__dirname, 'resources', 'plugin_rules');
|
const rulesPath = path.join(__dirname, 'resources', 'metadata');
|
||||||
const [_, aggregates] = buildIndexStore(rulesPath);
|
const [_, aggregates] = buildIndexStore(rulesPath);
|
||||||
expect(aggregates.tags).toEqual({"based-on-misra": 1,
|
expect(aggregates.tags).toEqual({"based-on-misra": 1,
|
||||||
"cert": 5,
|
"cert": 2,
|
||||||
"clumsy": 4,
|
"clumsy": 2,
|
||||||
"confusing": 4,
|
"confusing": 1,
|
||||||
"lock-in": 1,
|
"lock-in": 1,
|
||||||
"misra-c++2008": 1,
|
"misra-c++2008": 1,
|
||||||
"pitfall": 1});
|
"pitfall": 1
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('collects all languages', () => {
|
test('collects all languages', () => {
|
||||||
const rulesPath = path.join(__dirname, 'resources', 'plugin_rules');
|
const rulesPath = path.join(__dirname, 'resources', 'metadata');
|
||||||
const [_, aggregates] = buildIndexStore(rulesPath);
|
const [_, aggregates] = buildIndexStore(rulesPath);
|
||||||
expect(aggregates.langs).toEqual({"cfamily": 3,
|
expect(aggregates.langs).toEqual({"cfamily": 3,
|
||||||
"csharp": 1,
|
"csharp": 1,
|
||||||
|
"default": 3,
|
||||||
"java": 1,
|
"java": 1,
|
||||||
"python": 1});
|
"python": 1});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('collects all rule keys', () => {
|
test('collects all rule keys', () => {
|
||||||
const rulesPath = path.join(__dirname, 'resources', 'plugin_rules');
|
const rulesPath = path.join(__dirname, 'resources', 'metadata');
|
||||||
const [indexStore, _] = buildIndexStore(rulesPath);
|
const [indexStore, _] = buildIndexStore(rulesPath);
|
||||||
expect(indexStore['S3457'].all_keys).toEqual(['RSPEC-3457', 'S3457']);
|
expect(indexStore['S3457'].all_keys).toEqual(['RSPEC-3457', 'S3457']);
|
||||||
expect(indexStore['S1000'].all_keys).toEqual(['RSPEC-1000', 'S1000', 'UnnamedNamespaceInHeader']);
|
expect(indexStore['S1000'].all_keys).toEqual(['RSPEC-1000', 'S1000', 'UnnamedNamespaceInHeader']);
|
||||||
@ -63,7 +71,7 @@ describe('index store generation', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('collects all quality profiles', () => {
|
test('collects all quality profiles', () => {
|
||||||
const rulesPath = path.join(__dirname, 'resources', 'plugin_rules');
|
const rulesPath = path.join(__dirname, 'resources', 'metadata');
|
||||||
const [_, aggregates] = buildIndexStore(rulesPath);
|
const [_, aggregates] = buildIndexStore(rulesPath);
|
||||||
expect(aggregates.qualityProfiles).toEqual({
|
expect(aggregates.qualityProfiles).toEqual({
|
||||||
"MISRA C++ 2008 recommended": 2,
|
"MISRA C++ 2008 recommended": 2,
|
||||||
@ -72,7 +80,7 @@ describe('index store generation', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function createIndex() {
|
function createIndex() {
|
||||||
const rulesPath = path.join(__dirname, 'resources', 'plugin_rules');
|
const rulesPath = path.join(__dirname, 'resources', 'metadata');
|
||||||
const [indexStore, _] = buildIndexStore(rulesPath);
|
const [indexStore, _] = buildIndexStore(rulesPath);
|
||||||
|
|
||||||
// Hack to avoid warnings when 'selectivePipeline' is already registered
|
// 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', () => {
|
test('searches in rule tags', () => {
|
||||||
const searchIndex = createIndex();
|
const searchIndex = createIndex();
|
||||||
const searchesS3457 = search(searchIndex, 'cert', 'tags');
|
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');
|
const searchesS987 = search(searchIndex, 'based-on-misra', 'tags');
|
||||||
expect(searchesS987).toEqual(['S987']);
|
expect(searchesS987).toEqual(['S987']);
|
||||||
|
@ -2,7 +2,7 @@ import fs from 'fs';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import asciidoctor from 'asciidoctor';
|
import asciidoctor from 'asciidoctor';
|
||||||
|
|
||||||
import { getRulesDirectories, listSupportedLanguage } from './utils';
|
import { getRulesDirectories, listSupportedLanguages } from './utils';
|
||||||
import { logger } from './deploymentLogger';
|
import { logger } from './deploymentLogger';
|
||||||
|
|
||||||
const asciidoc = asciidoctor();
|
const asciidoc = asciidoctor();
|
||||||
@ -30,17 +30,17 @@ asciidoc.LoggerManager.setLogger(winstonLogger);
|
|||||||
* @param srcDir directory containing the original rule metadata and description.
|
* @param srcDir directory containing the original rule metadata and description.
|
||||||
* @param dstDir directory where the generated rules metadata and description will be written.
|
* @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 });
|
fs.mkdirSync(dstDir, { recursive: true });
|
||||||
const all_languages = listSupportedLanguage(srcDir);
|
const languages = listSupportedLanguages(srcDir);
|
||||||
let default_descr_wanted = true;
|
let default_descr_wanted = true;
|
||||||
for (const language of all_languages) {
|
for (const language of languages) {
|
||||||
const html = generate_rule_description(srcDir, language);
|
const html = generateRuleDescription(srcDir, language);
|
||||||
const dstFile = path.join(dstDir, language + "-description.html");
|
const dstFile = path.join(dstDir, language + '-description.html');
|
||||||
fs.writeFileSync(dstFile, html, {encoding: 'utf8'});
|
fs.writeFileSync(dstFile, html, {encoding: 'utf8'});
|
||||||
if (default_descr_wanted) {
|
if (default_descr_wanted) {
|
||||||
const dstFile = path.join(dstDir, "default-description.html");
|
const defFile = path.join(dstDir, 'default-description.html');
|
||||||
fs.writeFileSync(dstFile, html, {encoding: 'utf8'});
|
fs.writeFileSync(defFile, html, {encoding: 'utf8'});
|
||||||
default_descr_wanted = false;
|
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 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.
|
* @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)) {
|
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 srcDir rule's source directory.
|
||||||
* @param language language for which the metadata should be generated
|
* @param language language for which the metadata should be generated
|
||||||
*/
|
*/
|
||||||
function generate_rule_description(srcDir: string, language: string) {
|
function generateRuleDescription(srcDir: string, language: string) {
|
||||||
let ruleSrcFile = path.join(srcDir, language, "rule.adoc");
|
let ruleSrcFile = path.join(srcDir, language, 'rule.adoc');
|
||||||
if (!fs.existsSync(ruleSrcFile)) {
|
if (!fs.existsSync(ruleSrcFile)) {
|
||||||
ruleSrcFile = path.join(srcDir, "rule.adoc");
|
ruleSrcFile = path.join(srcDir, 'rule.adoc');
|
||||||
if (!fs.existsSync(ruleSrcFile)) {
|
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));
|
const baseDir = path.resolve(path.dirname(ruleSrcFile));
|
||||||
|
@ -5,8 +5,8 @@ import yargs from 'yargs/yargs';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import { clean_rules } from './clean';
|
import { clean_rules } from './clean';
|
||||||
import { generate_one_rule_metadata, generate_rules_metadata } from './metadata';
|
import { generateOneRuleMetadata, generateRulesMetadata } from './metadata';
|
||||||
import { generate_one_rule_description, generate_rules_description, } from './description';
|
import { generateOneRuleDescription, generateRulesDescription, } from './description';
|
||||||
import { createIndexFiles } from './searchIndex';
|
import { createIndexFiles } from './searchIndex';
|
||||||
import { process_incomplete_rspecs, PullRequest } from './pullRequestIndexing';
|
import { process_incomplete_rspecs, PullRequest } from './pullRequestIndexing';
|
||||||
|
|
||||||
@ -24,10 +24,10 @@ yargs(process.argv.slice(2))
|
|||||||
yargs.array<string>('rules')
|
yargs.array<string>('rules')
|
||||||
},
|
},
|
||||||
(argv: any) => {
|
(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) {
|
process_incomplete_rspecs(PR_DIRECTORY, function (srcDir: string, pr: PullRequest) {
|
||||||
const dstDir = path.join(RULE_DST_DIRECTORY, pr.rspec_id);
|
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')
|
yargs.array<string>('rules')
|
||||||
},
|
},
|
||||||
(argv: any) => {
|
(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) {
|
process_incomplete_rspecs(PR_DIRECTORY, function (srcDir: string, pr: PullRequest) {
|
||||||
const dstDir = path.join(RULE_DST_DIRECTORY, pr.rspec_id);
|
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',
|
.command('*', 'generate rules metadata, description and index',
|
||||||
() => {},
|
() => {},
|
||||||
(argv: any) => {
|
(argv: any) => {
|
||||||
generate_rules_metadata(RULE_SRC_DIRECTORY, RULE_DST_DIRECTORY);
|
generateRulesMetadata(RULE_SRC_DIRECTORY, RULE_DST_DIRECTORY);
|
||||||
generate_rules_description(RULE_SRC_DIRECTORY, RULE_DST_DIRECTORY);
|
generateRulesDescription(RULE_SRC_DIRECTORY, RULE_DST_DIRECTORY);
|
||||||
process_incomplete_rspecs(PR_DIRECTORY, function (srcDir: string, pr: PullRequest) {
|
process_incomplete_rspecs(PR_DIRECTORY, function (srcDir: string, pr: PullRequest) {
|
||||||
const dstDir = path.join(RULE_DST_DIRECTORY, pr.rspec_id);
|
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);
|
||||||
generate_one_rule_description(srcDir, dstDir);
|
generateOneRuleDescription(srcDir, dstDir);
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
createIndexFiles(RULE_DST_DIRECTORY);
|
createIndexFiles(RULE_DST_DIRECTORY);
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
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.
|
* 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 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.
|
* @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) {
|
branch: string, prUrl?: string) {
|
||||||
fs.mkdirSync(dstDir, { recursive: true });
|
fs.mkdirSync(dstDir, { recursive: true });
|
||||||
const allLanguages = listSupportedLanguage(srcDir);
|
const allLanguages = listSupportedLanguages(srcDir);
|
||||||
const allMetadata = allLanguages.map((language) => {
|
const allMetadata = allLanguages.map((language) => {
|
||||||
const metadata = generate_rule_metadata(srcDir, language, allLanguages);
|
const metadata = generateRuleMetadata(srcDir, language);
|
||||||
return {language, metadata};
|
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.
|
// Merge all sqKeys in an array so that we can use it later to check rule coverage.
|
||||||
const allKeys = allMetadata
|
const allKeys = allMetadata
|
||||||
.reduce((set, {metadata}) => {
|
.reduce((set, {metadata}) => {
|
||||||
@ -33,14 +38,15 @@ export function generate_one_rule_metadata(srcDir: string, dstDir: string,
|
|||||||
metadata.prUrl = prUrl;
|
metadata.prUrl = prUrl;
|
||||||
}
|
}
|
||||||
metadata.branch = branch;
|
metadata.branch = branch;
|
||||||
|
metadata.languagesSupport = languageSupports;
|
||||||
});
|
});
|
||||||
|
|
||||||
let default_metadata_wanted = true;
|
let default_metadata_wanted = true;
|
||||||
for (const { language, metadata } of allMetadata) {
|
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' })
|
fs.writeFileSync(dstJsonFile, JSON.stringify(metadata, null, 2), { encoding: 'utf8' })
|
||||||
if (default_metadata_wanted) {
|
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' });
|
fs.writeFileSync(dstFile, JSON.stringify(metadata, null, 2), { encoding: 'utf8' });
|
||||||
default_metadata_wanted = false;
|
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 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.
|
* @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)) {
|
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.
|
* Generate the metadata corresponding to one rule and one language.
|
||||||
* @param srcDir rule's source directory.
|
* @param srcDir rule's source directory.
|
||||||
* @param language language for which the metadata should be generated
|
* @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[]) {
|
function generateRuleMetadata(srcDir: string, language: string) {
|
||||||
let parentFile = path.join(srcDir, language, "metadata.json");
|
const parentFile = path.join(srcDir, language, 'metadata.json');
|
||||||
const parentJson = fs.existsSync(parentFile) ? JSON.parse(fs.readFileSync(parentFile, 'utf8')) : {};
|
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 childJson = fs.existsSync(childFile) ? JSON.parse(fs.readFileSync(childFile, 'utf8')) : {};
|
||||||
const mergedJson = {...childJson, ...parentJson};
|
return {...childJson, ...parentJson};
|
||||||
mergedJson["all_languages"] = all_languages;
|
|
||||||
return mergedJson;
|
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import lunr, { Token } from 'lunr';
|
|||||||
|
|
||||||
import { IndexedRule, IndexStore, Severity, IndexAggregates } from '../types/IndexStore';
|
import { IndexedRule, IndexStore, Severity, IndexAggregates } from '../types/IndexStore';
|
||||||
import { logger as rootLogger } from './deploymentLogger';
|
import { logger as rootLogger } from './deploymentLogger';
|
||||||
|
import { LanguageSupport } from '../types/RuleMetadata';
|
||||||
|
|
||||||
const logger = rootLogger.child({ source: path.basename(__filename) })
|
const logger = rootLogger.child({ source: path.basename(__filename) })
|
||||||
|
|
||||||
@ -17,33 +18,16 @@ export interface IndexedRuleWithDescription extends IndexedRule {
|
|||||||
descriptions?: Array<string>;
|
descriptions?: Array<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function buildOneRuleRecord(allLanguages: string[], rulesPath: string, ruleDir: 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]);
|
|
||||||
|
|
||||||
let types = new Set<string>();
|
let types = new Set<string>();
|
||||||
let severities = new Set<Severity>();
|
let severities = new Set<Severity>();
|
||||||
const all_keys = new Set<string>([ruleDir]);
|
const allKeys = new Set<string>([ruleDir]);
|
||||||
const titles = new Set<string>();
|
const titles = new Set<string>();
|
||||||
const tags = new Set<string>();
|
const tags = new Set<string>();
|
||||||
const qualityProfiles = new Set<string>();
|
const qualityProfiles = new Set<string>();
|
||||||
const descriptions = new Set<string>();
|
const descriptions = new Set<string>();
|
||||||
|
const supportedLanguages : LanguageSupport[] = [];
|
||||||
let prUrl = undefined;
|
let prUrl = undefined;
|
||||||
|
|
||||||
allLanguages.forEach((lang) => {
|
allLanguages.forEach((lang) => {
|
||||||
@ -63,11 +47,12 @@ export function buildIndexStore(rulesPath: string):[Record<string,IndexedRuleWit
|
|||||||
if (metadata.prUrl) {
|
if (metadata.prUrl) {
|
||||||
prUrl = metadata.prUrl;
|
prUrl = metadata.prUrl;
|
||||||
}
|
}
|
||||||
all_keys.add(metadata.sqKey);
|
allKeys.add(metadata.sqKey);
|
||||||
all_keys.add(metadata.ruleSpecification);
|
allKeys.add(metadata.ruleSpecification);
|
||||||
titles.add(metadata.title);
|
titles.add(metadata.title);
|
||||||
types.add(metadata.type);
|
types.add(metadata.type);
|
||||||
severities.add(metadata.defaultSeverity as Severity);
|
severities.add(metadata.defaultSeverity as Severity);
|
||||||
|
supportedLanguages.push({name: lang, status: metadata.status});
|
||||||
if (metadata.tags) {
|
if (metadata.tags) {
|
||||||
for (const tag of metadata.tags) {
|
for (const tag of metadata.tags) {
|
||||||
tags.add(tag);
|
tags.add(tag);
|
||||||
@ -78,60 +63,103 @@ export function buildIndexStore(rulesPath: string):[Record<string,IndexedRuleWit
|
|||||||
qualityProfiles.add(qualityProfile);
|
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;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
return {
|
||||||
qualityProfiles.forEach((qualityProfile) => {
|
types,
|
||||||
if (qualityProfile in allQualityProfiles) {
|
severities,
|
||||||
allQualityProfiles[qualityProfile] += 1;
|
allKeys,
|
||||||
} else {
|
titles,
|
||||||
allQualityProfiles[qualityProfile] = 1;
|
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) {
|
if (allLanguages.length < 1) {
|
||||||
logger.error(`No languages found for rule ${ruleDir}, at least 1 is required`);
|
logger.error(`No languages found for rule ${ruleDir}, at least 1 is required`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (types.size !== 1) {
|
if (record.types.size !== 1) {
|
||||||
logger.error(`${types.size} type(s) found for rule ${ruleDir}, 1 is required: ${JSON.stringify(types)}`);
|
logger.error(
|
||||||
|
`${record.types.size} type(s) found for rule ${ruleDir}, 1 is required: ${JSON.stringify(record.types)}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (severities.size < 1) {
|
if (record.severities.size < 1) {
|
||||||
logger.error(`No severity found for rule ${ruleDir}, at least 1 is required`);
|
logger.error(`No severity found for rule ${ruleDir}, at least 1 is required`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const indexedRecord: IndexedRuleWithDescription = {
|
const indexedRecord: IndexedRuleWithDescription = {
|
||||||
id: ruleDir,
|
id: ruleDir,
|
||||||
languages: allLanguages.sort(),
|
languages: Array.from(record.supportedLanguages).sort(),
|
||||||
type: types.values().next().value,
|
type: record.types.values().next().value,
|
||||||
severities: Array.from(severities).sort(),
|
severities: Array.from(record.severities).sort(),
|
||||||
all_keys: Array.from(all_keys).sort(),
|
all_keys: Array.from(record.allKeys).sort(),
|
||||||
titles: Array.from(titles).sort(),
|
titles: Array.from(record.titles).sort(),
|
||||||
tags: Array.from(tags).sort(),
|
tags: Array.from(record.tags).sort(),
|
||||||
qualityProfiles: Array.from(qualityProfiles).sort(),
|
qualityProfiles: Array.from(record.qualityProfiles).sort(),
|
||||||
descriptions: Array.from(descriptions).sort(),
|
descriptions: Array.from(record.descriptions).sort(),
|
||||||
prUrl
|
prUrl: record.prUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
return [ruleDir, indexedRecord];
|
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][];
|
const filteredRecords = indexedRecords.filter((value) => value !== null) as [string, IndexedRuleWithDescription][];
|
||||||
|
return [Object.fromEntries(filteredRecords), buildIndexAggregates(filteredRecords)];
|
||||||
return [Object.fromEntries(filteredRecords),
|
|
||||||
{langs: allLangs, tags: allTags, qualityProfiles: allQualityProfiles}];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -154,7 +182,7 @@ export function buildSearchIndex(ruleIndexStore: IndexStore) {
|
|||||||
|
|
||||||
lunr.Pipeline.registerFunction(selectivePipeline, 'selectivePipeline');
|
lunr.Pipeline.registerFunction(selectivePipeline, 'selectivePipeline');
|
||||||
|
|
||||||
var ruleIndex = lunr(function () {
|
return lunr(function () {
|
||||||
// Set our own token processing pipeline
|
// Set our own token processing pipeline
|
||||||
this.pipeline.reset();
|
this.pipeline.reset();
|
||||||
this.pipeline.add(selectivePipeline);
|
this.pipeline.add(selectivePipeline);
|
||||||
@ -175,8 +203,6 @@ export function buildSearchIndex(ruleIndexStore: IndexStore) {
|
|||||||
this.add(transformedRecord);
|
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.
|
* List every language for which a rule has a specialization, i.e. a sub-directory.
|
||||||
* @param ruleDirectory the rule's source directory
|
* @param ruleDirectory the rule's source directory
|
||||||
*/
|
*/
|
||||||
export function listSupportedLanguage(ruleDirectory: string) {
|
export function listSupportedLanguages(ruleDirectory: string): string[] {
|
||||||
return fs.readdirSync(ruleDirectory)
|
return fs.readdirSync(ruleDirectory)
|
||||||
.filter(fileName => fs.lstatSync(path.join(ruleDirectory, fileName)).isDirectory())
|
.filter(fileName => fs.lstatSync(path.join(ruleDirectory, fileName)).isDirectory())
|
||||||
.sort();
|
.sort();
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
import {LanguageSupport} from './RuleMetadata';
|
||||||
|
|
||||||
export type Severity = 'Blocker'|'Critical'|'Major'|'Minor'|'Info';
|
export type Severity = 'Blocker'|'Critical'|'Major'|'Minor'|'Info';
|
||||||
|
|
||||||
export interface IndexedRule {
|
export interface IndexedRule {
|
||||||
id: string;
|
id: string;
|
||||||
languages: string[];
|
languages: LanguageSupport[];
|
||||||
// FIXME: type, defaultSeverity should never be null but the index generation has a bug
|
// FIXME: type, defaultSeverity should never be null but the index generation has a bug
|
||||||
type: 'BUG'|'CODE_SMELL'|'VULNERABILITY'|'SECURITY_HOTSPOT';
|
type: 'BUG'|'CODE_SMELL'|'VULNERABILITY'|'SECURITY_HOTSPOT';
|
||||||
severities: Severity[];
|
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 {
|
export default interface RuleMetadata {
|
||||||
title: string,
|
title: string,
|
||||||
all_languages: string[],
|
languagesSupport: LanguageSupport[],
|
||||||
allKeys: string[],
|
allKeys: string[],
|
||||||
branch: string,
|
branch: string,
|
||||||
prUrl?: string
|
prUrl?: string
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { useFetch } from './useFetch';
|
import { useFetch } from './useFetch';
|
||||||
|
import { Status } from '../types/RuleMetadata';
|
||||||
|
|
||||||
type Version = string | { since: string, until: string };
|
type Version = string | { since: string, until: string };
|
||||||
type RuleCoverage = Record<string, Record<string, Version>>;
|
type RuleCoverage = Record<string, Record<string, Version>>;
|
||||||
@ -77,7 +78,28 @@ export function useRuleCoverage() {
|
|||||||
return ruleCoverageForSonarpediaKeys(allLanguageKeys, ruleKeys, mapper);
|
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);
|
const languageKeys = languageToSonarpedia.get(language);
|
||||||
if (!languageKeys || coveredRulesError || coveredRulesIsLoading) {
|
if (!languageKeys || coveredRulesError || coveredRulesIsLoading) {
|
||||||
if (coveredRulesError) {
|
if (coveredRulesError) {
|
||||||
@ -98,15 +120,7 @@ export function useRuleCoverage() {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.length > 0) {
|
return analyzerStateFromCoverageAndStatus(result, status);
|
||||||
// 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 {ruleCoverage, allLangsRuleCoverage, ruleStateInAnalyzer};
|
return {ruleCoverage, allLangsRuleCoverage, ruleStateInAnalyzer};
|
||||||
@ -119,13 +133,23 @@ export const RULE_STATE = {
|
|||||||
'darker': '#25699d'
|
'darker': '#25699d'
|
||||||
},
|
},
|
||||||
'targeted': {
|
'targeted': {
|
||||||
// orange
|
// blueish green
|
||||||
'color': '#FD7D20',
|
'color': '#8aa8a6',
|
||||||
'darker': '#E26003'
|
'darker': '#6c9390'
|
||||||
},
|
},
|
||||||
'removed': {
|
'removed': {
|
||||||
// red
|
// red
|
||||||
'color': '#C72B28',
|
'color': '#C72B28',
|
||||||
'darker': '#8D1B19'
|
'darker': '#8D1B19'
|
||||||
|
},
|
||||||
|
'deprecated' : {
|
||||||
|
// orange
|
||||||
|
'color': '#FD7D20',
|
||||||
|
'darker': '#E26003'
|
||||||
|
},
|
||||||
|
'closed' : {
|
||||||
|
// dark grey
|
||||||
|
'color': '#505050',
|
||||||
|
'darker': '#202020'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user