Compare commits
No commits in common. "master" and "rspec-id-counter" have entirely different histories.
master
...
rspec-id-c
@ -1,4 +0,0 @@
|
||||
load("github.com/SonarSource/cirrus-modules@v3", "load_features")
|
||||
|
||||
def main(ctx):
|
||||
return load_features(ctx)
|
137
.cirrus.yml
137
.cirrus.yml
@ -1,137 +0,0 @@
|
||||
env:
|
||||
GITHUB_TOKEN: VAULT[development/github/token/${CIRRUS_REPO_OWNER}-${CIRRUS_REPO_NAME}-ro token]
|
||||
SONAR_HOST_URL: VAULT[development/kv/data/next data.url]
|
||||
SONAR_TOKEN: VAULT[development/kv/data/next data.token]
|
||||
SONAR_SCANNER_VERSION: 5.0.1.3006
|
||||
SONAR_SCANNER_HOME: ${HOME}/.sonar/sonar-scanner-${SONAR_SCANNER_VERSION}-linux
|
||||
PATH: ${SONAR_SCANNER_HOME}/bin:$PATH
|
||||
# Need to clone full depth to track the changed files: for SQ analysis and for validation tasks
|
||||
CIRRUS_CLONE_DEPTH: 0
|
||||
# Use bash (instead of sh on linux or cmd.exe on windows)
|
||||
CIRRUS_SHELL: bash
|
||||
BASE_BRANCH: ${CIRRUS_BASE_BRANCH}
|
||||
DEFAULT_BRANCH: ${CIRRUS_DEFAULT_BRANCH}
|
||||
|
||||
|
||||
container_definition: &CONTAINER_DEFINITION
|
||||
cluster_name: ${CIRRUS_CLUSTER_NAME}
|
||||
builder_role: cirrus-builder
|
||||
builder_image: docker-builder-v*
|
||||
builder_instance_type: t3.small
|
||||
builder_subnet_id: ${CIRRUS_AWS_SUBNET}
|
||||
region: eu-central-1
|
||||
namespace: default
|
||||
use_in_memory_disk: true
|
||||
|
||||
setup_sonar_scanner: &SETUP_SONAR_SCANNER
|
||||
setup_sonar_scanner_script:
|
||||
- apt update -y && apt upgrade -y && apt update -y && apt install -y unzip
|
||||
- curl --create-dirs -sSLo $HOME/.sonar/sonar-scanner.zip https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$SONAR_SCANNER_VERSION-linux.zip
|
||||
- unzip -o $HOME/.sonar/sonar-scanner.zip -d $HOME/.sonar/
|
||||
|
||||
tooling_tests_task:
|
||||
eks_container:
|
||||
<<: *CONTAINER_DEFINITION
|
||||
dockerfile: ci/Dockerfile
|
||||
cpu: 1
|
||||
memory: 2G
|
||||
env:
|
||||
PYTHONPATH: .
|
||||
install_dependencies_script:
|
||||
- ci/install_rspec_tools_dependencies.sh
|
||||
tests_script:
|
||||
- bash ci/fetch_branches.sh
|
||||
- cd rspec-tools
|
||||
- pipenv run pytest --cov=rspec_tools --cov-report=xml
|
||||
<<: *SETUP_SONAR_SCANNER
|
||||
analyze_script:
|
||||
- cd rspec-tools
|
||||
- sonar-scanner
|
||||
|
||||
|
||||
frontend_tests_task:
|
||||
eks_container:
|
||||
<<: *CONTAINER_DEFINITION
|
||||
dockerfile: ci/frontend-tests-dockerfile
|
||||
cpu: 1
|
||||
memory: 3G
|
||||
node_modules_cache:
|
||||
folder: frontend/node_modules
|
||||
reupload_on_changes: false # since there is a fingerprint script
|
||||
fingerprint_script:
|
||||
- echo $CIRRUS_OS
|
||||
- node --version
|
||||
- cat frontend/package-lock.json
|
||||
populate_script:
|
||||
- cd frontend
|
||||
- npm install
|
||||
tests_script:
|
||||
- bash ci/fetch_branches.sh
|
||||
- cd frontend
|
||||
- npm run build
|
||||
- npm test -- --detectOpenHandles --coverage .
|
||||
<<: *SETUP_SONAR_SCANNER
|
||||
analyze_script:
|
||||
- cd frontend
|
||||
- sonar-scanner
|
||||
|
||||
validate_ci_tests_task:
|
||||
skip: "!changesInclude('ci_tests/**', 'ci/**')"
|
||||
eks_container:
|
||||
<<: *CONTAINER_DEFINITION
|
||||
dockerfile: ci/Dockerfile
|
||||
cpu: 1
|
||||
memory: 2G
|
||||
ci_tests_script:
|
||||
- ./ci_tests/asciidoc_validation/run_tests.sh
|
||||
|
||||
validate_rules_task:
|
||||
eks_container:
|
||||
<<: *CONTAINER_DEFINITION
|
||||
dockerfile: ci/Dockerfile
|
||||
cpu: 1
|
||||
memory: 2G
|
||||
metadata_validation_script:
|
||||
- ./ci/validate_metadata.sh
|
||||
file_extensions_validation_script:
|
||||
- ./ci/validate_file_extensions.sh
|
||||
asciidoc_validation_script:
|
||||
- ./ci/validate_asciidoc.sh
|
||||
|
||||
validate_links_task:
|
||||
timeout_in: 120m
|
||||
execution_lock: RSPEC_validate_links
|
||||
eks_container:
|
||||
<<: *CONTAINER_DEFINITION
|
||||
dockerfile: ci/Dockerfile
|
||||
cpu: 1
|
||||
memory: 2G
|
||||
env:
|
||||
LINK_CACHE_NAME: link-probing-status
|
||||
LINK_CACHE_PATH: /root/link-probing-history.cache
|
||||
cache_download_script:
|
||||
- bash ci/cirrus-cache.sh download ${LINK_CACHE_NAME} ${LINK_CACHE_PATH}
|
||||
- md5sum /root/link-probing-history.cache/link_probes.history || true
|
||||
tests_script:
|
||||
- ./ci/validate_links.sh ${LINK_CACHE_PATH}
|
||||
always:
|
||||
cache_upload_script:
|
||||
- md5sum /root/link-probing-history.cache/link_probes.history || true
|
||||
- bash ci/cirrus-cache.sh upload ${LINK_CACHE_NAME} ${LINK_CACHE_PATH}
|
||||
|
||||
all_required_checks_task:
|
||||
depends_on:
|
||||
- tooling_tests
|
||||
- frontend_tests
|
||||
- validate_rules
|
||||
- validate_ci_tests
|
||||
eks_container:
|
||||
<<: *CONTAINER_DEFINITION
|
||||
dockerfile: ci/Dockerfile
|
||||
cpu: 1
|
||||
memory: 1G
|
||||
clone_script:
|
||||
- echo 'This is a dummy task used to wait on other tasks. Clone is not necessary.'
|
||||
print_message_script:
|
||||
- echo 'All required checks have passed'
|
||||
|
@ -1,16 +0,0 @@
|
||||
{
|
||||
"build": {
|
||||
"dockerfile": "../ci/Dockerfile"
|
||||
},
|
||||
// https://code.visualstudio.com/docs/devcontainers/create-dev-container#_rebuild
|
||||
"postCreateCommand": ".devcontainer/finalize-container.sh",
|
||||
"waitFor": "postCreateCommand",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"asciidoctor.asciidoctor-vscode"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
TOP_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)/..
|
||||
|
||||
$TOP_DIR/ci/install_rspec_tools_dependencies.sh
|
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@ -1 +0,0 @@
|
||||
.github/CODEOWNERS @sonarsource/quality-cfamily-squad
|
18
.github/pull_request_template.md
vendored
18
.github/pull_request_template.md
vendored
@ -1,18 +0,0 @@
|
||||
<!--
|
||||
Jira Automation:
|
||||
|
||||
* Mention existing issue in the PR title to move it around automatically.
|
||||
* Mention existing issue in the PR description and a sub-task will be created for you to track this rspec PR separately.
|
||||
|
||||
No issue is created by default.
|
||||
-->
|
||||
|
||||
## Review
|
||||
|
||||
A dedicated reviewer checked the rule description successfully for:
|
||||
|
||||
- [ ] logical errors and incorrect information
|
||||
- [ ] information gaps and missing content
|
||||
- [ ] text style and tone
|
||||
- [ ] PR summary and labels follow [the guidelines](https://github.com/SonarSource/rspec/#to-modify-an-existing-rule)
|
||||
|
28
.github/workflows/PullRequestClosed.yml
vendored
28
.github/workflows/PullRequestClosed.yml
vendored
@ -1,28 +0,0 @@
|
||||
name: Pull Request Closed
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
|
||||
jobs:
|
||||
PullRequestMerged_job:
|
||||
name: Pull Request Merged
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
pull-requests: read
|
||||
# For external PR, ticket should be moved manually
|
||||
if: |
|
||||
github.event.pull_request.head.repo.full_name == github.repository
|
||||
steps:
|
||||
- id: secrets
|
||||
uses: SonarSource/vault-action-wrapper@v3
|
||||
with:
|
||||
secrets: |
|
||||
development/kv/data/jira user | JIRA_USER;
|
||||
development/kv/data/jira token | JIRA_TOKEN;
|
||||
- uses: sonarsource/gh-action-lt-backlog/PullRequestClosed@v2
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
jira-user: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_USER }}
|
||||
jira-token: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_TOKEN }}
|
28
.github/workflows/PullRequestCreated.yml
vendored
28
.github/workflows/PullRequestCreated.yml
vendored
@ -1,28 +0,0 @@
|
||||
name: Pull Request Created
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: ["opened"]
|
||||
|
||||
jobs:
|
||||
PullRequestCreated_job:
|
||||
name: Pull Request Created
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
# For external PR, ticket should be created manually
|
||||
if: |
|
||||
github.event.pull_request.head.repo.full_name == github.repository
|
||||
steps:
|
||||
- id: secrets
|
||||
uses: SonarSource/vault-action-wrapper@v3
|
||||
with:
|
||||
secrets: |
|
||||
development/github/token/{REPO_OWNER_NAME_DASH}-jira token | GITHUB_TOKEN;
|
||||
development/kv/data/jira user | JIRA_USER;
|
||||
development/kv/data/jira token | JIRA_TOKEN;
|
||||
- uses: sonarsource/gh-action-lt-backlog/PullRequestCreated@v2
|
||||
with:
|
||||
github-token: ${{ fromJSON(steps.secrets.outputs.vault).GITHUB_TOKEN }}
|
||||
jira-user: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_USER }}
|
||||
jira-token: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_TOKEN }}
|
28
.github/workflows/RequestReview.yml
vendored
28
.github/workflows/RequestReview.yml
vendored
@ -1,28 +0,0 @@
|
||||
name: Request review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: ["review_requested"]
|
||||
|
||||
jobs:
|
||||
RequestReview_job:
|
||||
name: Request review
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
# For external PR, ticket should be moved manually
|
||||
if: |
|
||||
github.event.pull_request.head.repo.full_name == github.repository
|
||||
steps:
|
||||
- id: secrets
|
||||
uses: SonarSource/vault-action-wrapper@v3
|
||||
with:
|
||||
secrets: |
|
||||
development/github/token/{REPO_OWNER_NAME_DASH}-jira token | GITHUB_TOKEN;
|
||||
development/kv/data/jira user | JIRA_USER;
|
||||
development/kv/data/jira token | JIRA_TOKEN;
|
||||
- uses: sonarsource/gh-action-lt-backlog/RequestReview@v2
|
||||
with:
|
||||
github-token: ${{ fromJSON(steps.secrets.outputs.vault).GITHUB_TOKEN }}
|
||||
jira-user: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_USER }}
|
||||
jira-token: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_TOKEN }}
|
30
.github/workflows/SubmitReview.yml
vendored
30
.github/workflows/SubmitReview.yml
vendored
@ -1,30 +0,0 @@
|
||||
name: Submit Review
|
||||
|
||||
on:
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
jobs:
|
||||
SubmitReview_job:
|
||||
name: Submit Review
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
pull-requests: read
|
||||
# For external PR, ticket should be moved manually
|
||||
if: |
|
||||
github.event.pull_request.head.repo.full_name == github.repository
|
||||
&& (github.event.review.state == 'changes_requested'
|
||||
|| github.event.review.state == 'approved')
|
||||
steps:
|
||||
- id: secrets
|
||||
uses: SonarSource/vault-action-wrapper@v3
|
||||
with:
|
||||
secrets: |
|
||||
development/kv/data/jira user | JIRA_USER;
|
||||
development/kv/data/jira token | JIRA_TOKEN;
|
||||
- uses: sonarsource/gh-action-lt-backlog/SubmitReview@v2
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
jira-user: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_USER }}
|
||||
jira-token: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_TOKEN }}
|
45
.github/workflows/add_language.yml
vendored
45
.github/workflows/add_language.yml
vendored
@ -1,45 +0,0 @@
|
||||
name: Add language to a rule
|
||||
|
||||
# Workflow runs when manually triggered using the UI or API.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# Inputs the workflow accepts.
|
||||
inputs:
|
||||
rule:
|
||||
description: 'ID of an existing rule (e.g., S1234).'
|
||||
required: true
|
||||
language:
|
||||
description: 'Language to be added to the rule, (e.g., cfamily)'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
add_language_to_rule:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: master
|
||||
path: 'rspec'
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.9'
|
||||
|
||||
- name: 'Install Pipenv'
|
||||
run: |
|
||||
pip install pipenv
|
||||
|
||||
- name: 'Install rspec-tools'
|
||||
working-directory: 'rspec/rspec-tools'
|
||||
run: pipenv install
|
||||
|
||||
- name: 'Add Language'
|
||||
working-directory: 'rspec/rspec-tools'
|
||||
run: pipenv run rspec-tools add-lang-to-rule --user ${{ github.actor }} --language "${{ github.event.inputs.language }}" --rule "${{ github.event.inputs.rule }}"
|
42
.github/workflows/create_new_rspec.yml
vendored
42
.github/workflows/create_new_rspec.yml
vendored
@ -1,42 +0,0 @@
|
||||
name: Create New RSPEC
|
||||
|
||||
# Workflow runs when manually triggered using the UI or API.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# Inputs the workflow accepts.
|
||||
inputs:
|
||||
languages:
|
||||
description: 'Comma-separated list of targeted languages'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
create_new_rule:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: master
|
||||
path: 'rspec'
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.9'
|
||||
|
||||
- name: 'Install Pipenv'
|
||||
run: |
|
||||
pip install pipenv
|
||||
|
||||
- name: 'Install rspec-tools'
|
||||
working-directory: 'rspec/rspec-tools'
|
||||
run: pipenv install
|
||||
|
||||
- name: 'Create Rule'
|
||||
working-directory: 'rspec/rspec-tools'
|
||||
run: pipenv run rspec-tools create-rule --user ${{ github.actor }} --languages "${{ github.event.inputs.languages }}"
|
20
.github/workflows/ensure_label.yml
vendored
20
.github/workflows/ensure_label.yml
vendored
@ -1,20 +0,0 @@
|
||||
name: PR should have a language label
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, labeled, unlabeled, synchronize]
|
||||
|
||||
jobs:
|
||||
label:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: mheap/github-action-required-labels@v5
|
||||
with:
|
||||
mode: minimum
|
||||
count: 1
|
||||
add_comment: true
|
||||
use_regex: true
|
||||
labels: ".*"
|
||||
message: "Please add a label with the relevant language(s) to be able to merge this PR"
|
38
.github/workflows/main.yml
vendored
38
.github/workflows/main.yml
vendored
@ -1,38 +0,0 @@
|
||||
name: Build and Deploy
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- rule/**
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: read # Get the list and metadata of open new-rule PRs
|
||||
contents: write # Get the contents of open new-rule PRs, the 'master'; write to 'gh-pages' branch
|
||||
steps:
|
||||
- name: Checkout 🛎️
|
||||
uses: actions/checkout@v4 # If you're using actions/checkout you must set persist-credentials to false in most cases for the deployment to work correctly.
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: 'master'
|
||||
|
||||
- name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built.
|
||||
working-directory: frontend
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install libkrb5-dev -y
|
||||
npm install
|
||||
npm run predeploy
|
||||
env:
|
||||
NODE_OPTIONS: "--max-old-space-size=3048"
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Deploy 🚀
|
||||
uses: JamesIves/github-pages-deploy-action@releases/v3
|
||||
with:
|
||||
SINGLE_COMMIT: true
|
||||
CLEAN: true
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BRANCH: gh-pages # The branch the action should deploy to.
|
||||
FOLDER: frontend/build # The folder the action should deploy.
|
141
.github/workflows/update_coverage.yml
vendored
141
.github/workflows/update_coverage.yml
vendored
@ -1,141 +0,0 @@
|
||||
name: Update rule coverage
|
||||
on:
|
||||
schedule:
|
||||
- cron: '17 2 * * *'
|
||||
workflow_dispatch: # When manually triggered from a non-default branch, the results will not be pushed
|
||||
|
||||
jobs:
|
||||
update_coverage:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write # required by SonarSource/vault-action-wrapper
|
||||
contents: write
|
||||
actions: write # required by andymckay/cancel-action
|
||||
env:
|
||||
TMP_BRANCH: temporary/coverage_update
|
||||
|
||||
steps:
|
||||
- name: 'get secrets'
|
||||
id: secrets
|
||||
uses: SonarSource/vault-action-wrapper@v3
|
||||
with:
|
||||
secrets: |
|
||||
development/github/token/SonarSource-rspec-coverage token | coverage_github_token;
|
||||
development/kv/data/slack token | slack_token;
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: true
|
||||
fetch-depth: 0
|
||||
path: 'rspec'
|
||||
token: ${{ fromJSON(steps.secrets.outputs.vault).coverage_github_token }}
|
||||
ref: 'master'
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.9'
|
||||
|
||||
- name: 'Install Pipenv'
|
||||
run: pip install pipenv
|
||||
|
||||
- name: 'Install coverage script dependencies'
|
||||
working-directory: 'rspec/rspec-tools'
|
||||
run: |
|
||||
pipenv --python python3.9 install
|
||||
|
||||
- name: 'Regenerate coverage information'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).coverage_github_token }}
|
||||
id: gen-coverage
|
||||
working-directory: 'rspec/rspec-tools'
|
||||
run: |
|
||||
pipenv run rspec-tools update-coverage --rulesdir ../rules
|
||||
mv ./covered_rules.json ../frontend/public/covered_rules.json
|
||||
if git diff --exit-code ../frontend/public/covered_rules.json; then
|
||||
echo "new_coverage=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "new_coverage=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: 'Cancel if coverage did not change'
|
||||
if: steps.gen-coverage.outputs.new_coverage != 'true'
|
||||
uses: andymckay/cancel-action@0.2
|
||||
|
||||
- name: 'Push the updated coverage file to a new branch'
|
||||
id: create-temp-branch
|
||||
if: steps.gen-coverage.outputs.new_coverage == 'true'
|
||||
working-directory: 'rspec'
|
||||
run: |
|
||||
git config --global user.name "SonarTech"
|
||||
git config --global user.email "sonartech@sonarsource.com"
|
||||
git checkout -b $TMP_BRANCH
|
||||
git add frontend/public/covered_rules.json
|
||||
git commit -m "update coverage information"
|
||||
git push --force-with-lease origin $TMP_BRANCH
|
||||
|
||||
- name: 'Create a PR'
|
||||
id: create-github-pr
|
||||
working-directory: 'rspec'
|
||||
env:
|
||||
GH_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).coverage_github_token }}
|
||||
run: |
|
||||
PR_URL=$(gh pr create --head ${{ env.TMP_BRANCH }} --title "Update coverage information" --body "" --label "rspec system")
|
||||
gh pr merge $PR_URL
|
||||
|
||||
- name: 'Wait until the PR is merged'
|
||||
id: wait-for-pr-to-merge
|
||||
env:
|
||||
GH_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).coverage_github_token }}
|
||||
working-directory: 'rspec'
|
||||
run: |
|
||||
set -ueo pipefail
|
||||
|
||||
# Implicitly referring to the PR corresponding to current branch
|
||||
|
||||
# Set timeout (20 minutes in seconds)
|
||||
TIMEOUT=1200 # seconds
|
||||
START_TIME=$(date +%s)
|
||||
INTERVAL=20 # seconds
|
||||
|
||||
while true; do
|
||||
# Check if the PR is merged
|
||||
PR_STATE=$(gh pr view --json state,mergedAt -q '.state')
|
||||
MERGED_AT=$(gh pr view --json state,mergedAt -q '.mergedAt')
|
||||
|
||||
if [[ "${PR_STATE}" == "MERGED" ]]; then
|
||||
echo "PR merged at: $MERGED_AT"
|
||||
exit 0
|
||||
fi
|
||||
echo "PR state is ${PR_STATE}"
|
||||
|
||||
# Check for timeout
|
||||
CURRENT_TIME=$(date +%s)
|
||||
ELAPSED_TIME=$((CURRENT_TIME - START_TIME))
|
||||
|
||||
if [[ "${ELAPSED_TIME}" -gt "${TIMEOUT}" ]]; then
|
||||
echo "Timeout waiting for PR to merge."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Wait for $INTERVAL seconds before checking again
|
||||
sleep "$INTERVAL"
|
||||
done
|
||||
|
||||
- name: 'Close PR and delete branch upon failure to merge'
|
||||
if: ${{ failure() }}
|
||||
env:
|
||||
GH_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).coverage_github_token }}
|
||||
working-directory: 'rspec'
|
||||
run: |
|
||||
PR_URL=$(gh pr view --json url --jq '.url')
|
||||
gh pr close "$PR_URL" --delete-branch
|
||||
|
||||
- name: 'Notify on slack about the failure'
|
||||
if: ${{ failure() }}
|
||||
env:
|
||||
SLACK_API_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).slack_token }}
|
||||
working-directory: 'rspec/rspec-tools'
|
||||
run: |
|
||||
pipenv run rspec-tools notify-failure-on-slack \
|
||||
--message "ERROR: failed to update rule coverage. See https://github.com/SonarSource/rspec/actions/runs/$GITHUB_RUN_ID" \
|
||||
--channel team-analysis-rspec
|
55
.github/workflows/update_quickfix_status.yml
vendored
55
.github/workflows/update_quickfix_status.yml
vendored
@ -1,55 +0,0 @@
|
||||
name: Update quick fix status
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
rule:
|
||||
description: 'ID of an existing rule (e.g., S1234).'
|
||||
required: true
|
||||
type: string
|
||||
language:
|
||||
description: 'Language to be updated for the given rule, (e.g., cfamily)'
|
||||
required: true
|
||||
type: string
|
||||
status:
|
||||
description: 'The new status for quick fix (e.g., covered)'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- covered
|
||||
- partial
|
||||
- targeted
|
||||
- infeasible
|
||||
- unknown
|
||||
|
||||
jobs:
|
||||
update_quickfix_status:
|
||||
name: Update quick fix status
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: master
|
||||
path: 'rspec'
|
||||
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
|
||||
- name: 'Install Pipenv'
|
||||
run: |
|
||||
pip install pipenv
|
||||
|
||||
- name: 'Install rspec-tools'
|
||||
working-directory: 'rspec/rspec-tools'
|
||||
run: pipenv install
|
||||
|
||||
- name: 'Update quickfix status'
|
||||
working-directory: 'rspec/rspec-tools'
|
||||
run: pipenv run rspec-tools update-quickfix-status --user ${{ github.actor }} --rule "${{ github.event.inputs.rule }}" --language "${{ github.event.inputs.language }}" --status "${{ github.event.inputs.status }}"
|
22
.gitignore
vendored
22
.gitignore
vendored
@ -1,22 +0,0 @@
|
||||
# IDE
|
||||
.vs/
|
||||
|
||||
# IntelliJ IDEA
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
.idea/
|
||||
|
||||
# generated files
|
||||
/rules/**/*.html
|
||||
/frontend/public/rules
|
||||
rspec-tools/link_probes.history
|
||||
|
||||
# compiled files
|
||||
*.out
|
||||
*.obj
|
||||
*.pyc
|
||||
|
||||
# Eclipse
|
||||
.project
|
||||
.settings/
|
20
.vscode/settings.json
vendored
20
.vscode/settings.json
vendored
@ -1,20 +0,0 @@
|
||||
{
|
||||
"grammarly.selectors": [
|
||||
{
|
||||
"language": "markdown",
|
||||
"scheme": "file"
|
||||
},
|
||||
{
|
||||
"language": "asciidoc",
|
||||
"scheme": "file"
|
||||
}
|
||||
],
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": [
|
||||
"/rules/**/metadata.json"
|
||||
],
|
||||
"url": "./rspec-tools/rspec_tools/validation/rule-metadata-schema.json"
|
||||
}
|
||||
]
|
||||
}
|
184
LICENSE
184
LICENSE
@ -1,184 +0,0 @@
|
||||
SONAR Source-Available License v1.0
|
||||
Last Updated November 13, 2024
|
||||
|
||||
1. DEFINITIONS
|
||||
|
||||
"Agreement" means this Sonar Source-Available License v1.0
|
||||
|
||||
"Competing" means marketing a product or service as a substitute for the
|
||||
functionality or value of SonarQube. A product or service may compete regardless
|
||||
of how it is designed or deployed. For example, a product or service may compete
|
||||
even if it provides its functionality via any kind of interface (including
|
||||
services, libraries, or plug-ins), even if it is ported to a different platform
|
||||
or programming language, and even if it is provided free of charge.
|
||||
|
||||
"Contribution" means:
|
||||
|
||||
a) in the case of the initial Contributor, the initial content Distributed under
|
||||
this Agreement, and
|
||||
|
||||
b) in the case of each subsequent Contributor:
|
||||
i) changes to the Program, and
|
||||
ii) additions to the Program;
|
||||
|
||||
where such changes and/or additions to the Program originate from and are
|
||||
Distributed by that particular Contributor. A Contribution "originates" from a
|
||||
Contributor if it was added to the Program by such Contributor itself or anyone
|
||||
acting on such Contributor's behalf. Contributions do not include changes or
|
||||
additions to the Program that are not Modified Works.
|
||||
|
||||
"Contributor" means any person or entity that Distributes the Program.
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source Code or other form,
|
||||
that is based on (or derived from) the Program and for which the editorial
|
||||
revisions, annotations, elaborations, or other modifications represent, as a
|
||||
whole, an original work of authorship.
|
||||
|
||||
"Distribute" means the acts of a) distributing or b) making available in any
|
||||
manner that enables the transfer of a copy.
|
||||
|
||||
"Licensed Patents" mean patent claims licensable by a Contributor that are
|
||||
necessarily infringed by the use or sale of its Contribution alone or when
|
||||
combined with the Program.
|
||||
|
||||
"Modified Works" shall mean any work in Source Code or other form that results
|
||||
from an addition to, deletion from, or modification of the contents of the
|
||||
Program, including, for purposes of clarity, any new file in Source Code form
|
||||
that contains any contents of the Program. Modified Works shall not include
|
||||
works that contain only declarations, interfaces, types, classes, structures, or
|
||||
files of the Program solely in each case in order to link to, bind by name, or
|
||||
subclass the Program or Modified Works thereof.
|
||||
|
||||
"Non-competitive Purpose" means any purpose except for (a) providing to others
|
||||
any product or service that includes or offers the same or substantially similar
|
||||
functionality as SonarQube, (b) Competing with SonarQube, and/or (c) employing,
|
||||
using, or engaging artificial intelligence technology that is not part of the
|
||||
Program to ingest, interpret, analyze, train on, or interact with the data
|
||||
provided by the Program, or to engage with the Program in any manner.
|
||||
|
||||
"Notices" means any legal statements or attributions included with the Program,
|
||||
including, without limitation, statements concerning copyright, patent,
|
||||
trademark, disclaimers of warranty, or limitations of liability
|
||||
|
||||
"Program" means the Contributions Distributed in accordance with this Agreement.
|
||||
|
||||
"Recipient" means anyone who receives the Program under this Agreement,
|
||||
including Contributors.
|
||||
|
||||
"SonarQube" means an open-source or commercial edition of software offered by
|
||||
SonarSource that is branded "SonarQube".
|
||||
|
||||
"SonarSource" means SonarSource SA, a Swiss company registered in Switzerland
|
||||
under UID No. CHE-114.587.664.
|
||||
|
||||
"Source Code" means the form of a Program preferred for making modifications,
|
||||
including but not limited to software source code, documentation source, and
|
||||
configuration files.
|
||||
|
||||
2. GRANT OF RIGHTS
|
||||
|
||||
a) Subject to the terms of this Agreement, each Contributor hereby grants
|
||||
Recipient a non-exclusive, worldwide, royalty-free copyright license, for any
|
||||
Non-competitive Purpose, to reproduce, prepare Derivative Works of, publicly
|
||||
display, publicly perform, Distribute and sublicense the Contribution of such
|
||||
Contributor, if any, and such Derivative Works.
|
||||
|
||||
b) Subject to the terms of this Agreement, each Contributor hereby grants
|
||||
Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed
|
||||
Patents, for any Non-competitive Purpose, to make, use, sell, offer to sell,
|
||||
import, and otherwise transfer the Contribution of such Contributor, if any, in
|
||||
Source Code or other form. This patent license shall apply to the combination of
|
||||
the Contribution and the Program if, at the time the Contribution is added by
|
||||
the Contributor, such addition of the Contribution causes such combination to be
|
||||
covered by the Licensed Patents. The patent license shall not apply to any other
|
||||
combinations that include the Contribution.
|
||||
|
||||
c) Recipient understands that although each Contributor grants the licenses to
|
||||
its Contributions set forth herein, no assurances are provided by any
|
||||
Contributor that the Program does not infringe the patent or other intellectual
|
||||
property rights of any other entity. Each Contributor disclaims any liability to
|
||||
Recipient for claims brought by any other entity based on infringement of
|
||||
intellectual property rights or otherwise. As a condition to exercising the
|
||||
rights and licenses granted hereunder, each Recipient hereby assumes sole
|
||||
responsibility to secure any other intellectual property rights needed, if any.
|
||||
For example, if a third-party patent license is required to allow Recipient to
|
||||
Distribute the Program, it is Recipient's responsibility to acquire that license
|
||||
before distributing the Program.
|
||||
|
||||
d) Each Contributor represents that to its knowledge it has sufficient copyright
|
||||
rights in its Contribution, if any, to grant the copyright license set forth in
|
||||
this Agreement.
|
||||
|
||||
3. REQUIREMENTS
|
||||
|
||||
3.1 If a Contributor Distributes the Program in any form, then the Program must
|
||||
also be made available as Source Code, in accordance with section 3.2, and the
|
||||
Contributor must accompany the Program with a statement that the Source Code for
|
||||
the Program is available under this Agreement, and inform Recipients how to
|
||||
obtain it in a reasonable manner on or through a medium customarily used for
|
||||
software exchange; and
|
||||
|
||||
3.2 When the Program is Distributed as Source Code:
|
||||
|
||||
a) it must be made available under this Agreement, and
|
||||
|
||||
b) a copy of this Agreement must be included with each copy of the Program.
|
||||
|
||||
3.3 Contributors may not remove or alter any Notices contained within the
|
||||
Program from any copy of the Program which they Distribute, provided that
|
||||
Contributors may add their own appropriate Notices.
|
||||
|
||||
4. NO WARRANTY
|
||||
|
||||
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
|
||||
OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT
|
||||
LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
|
||||
responsible for determining the appropriateness of using and distributing the
|
||||
Program and assumes all risks associated with its exercise of rights under this
|
||||
Agreement, including but not limited to the risks and costs of program errors,
|
||||
compliance with applicable laws, damage to or loss of data, programs or
|
||||
equipment, and unavailability or interruption of operations.
|
||||
|
||||
5. DISCLAIMER OF LIABILITY
|
||||
|
||||
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF
|
||||
THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF
|
||||
THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
6. GENERAL
|
||||
|
||||
If any provision of this Agreement is invalid or unenforceable under applicable
|
||||
law, it shall not affect the validity or enforceability of the remainder of the
|
||||
terms of this Agreement, and without further action by the parties hereto, such
|
||||
provision shall be reformed to the minimum extent necessary to make such
|
||||
provision valid and enforceable.
|
||||
|
||||
If Recipient institutes patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Program itself
|
||||
(excluding combinations of the Program with other software or hardware)
|
||||
infringes such Recipient’s patent(s), then such Recipient’s rights granted under
|
||||
Section 2(b) shall terminate as of the date such litigation is filed.
|
||||
|
||||
All Recipient’s rights under this Agreement shall terminate if it fails to
|
||||
comply with any of the material terms or conditions of this Agreement and does
|
||||
not cure such failure in a reasonable period of time after becoming aware of
|
||||
such noncompliance. If all Recipient’s rights under this Agreement terminate,
|
||||
Recipient agrees to cease use and distribution of the Program as soon as
|
||||
reasonably practicable. However, Recipient’s obligations under this Agreement
|
||||
and any licenses granted by Recipient relating to the Program shall continue and
|
||||
survive.
|
||||
|
||||
Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives
|
||||
no rights or licenses to the intellectual property of any Contributor under this
|
||||
Agreement, whether expressly, by implication, estoppel, or otherwise. All rights
|
||||
in the Program not expressly granted under this Agreement are reserved. Nothing
|
||||
in this Agreement is intended to be enforceable by any entity that is not a
|
||||
Contributor or Recipient. No third-party beneficiary rights are created under
|
||||
this Agreement.
|
287
README.adoc
287
README.adoc
@ -1,287 +0,0 @@
|
||||
ifdef::env-github[]
|
||||
:warning-caption: :warning:
|
||||
:note-caption: :information_source:
|
||||
endif::[]
|
||||
= SonarSource Rule Specification repository
|
||||
|
||||
This repository contains the specification of every static-analysis rule available in SonarLint, SonarCloud, or SonarQube.
|
||||
|
||||
It also contains rules which have been dropped and rules which will one day be implemented.
|
||||
|
||||
The content of this repository is covered by the link:LICENSE[SONAR Source-Available License v1.0].
|
||||
|
||||
== Rules directory structure
|
||||
|
||||
* https://github.com/SonarSource/rspec/tree/master/rules[rules] directory: contains every specified rule.
|
||||
** `rules/Sxxxx`: contains every specification for rule `Sxxxx`.
|
||||
*** `rules/Sxxxx/*.adoc`: Asciidoc files which can be reused by multiple language-specific descriptions.
|
||||
*** `rules/Sxxxx/metadata.json`: rule metadata shared between language-specific RSPECs. Each language can override fields in its own `metadata.json` file. +
|
||||
It is thanks to this file that you can add `tags`, `securityStandards` etc... to your rule.
|
||||
*** `rules/Sxxxx/common`: contains common content shared by all the supported languages. It is organized in the following subdirectories:
|
||||
**** `rules/Sxxxx/common/fix`
|
||||
**** `rules/Sxxxx/common/images`
|
||||
**** `rules/Sxxxx/common/pitfalls`
|
||||
**** `rules/Sxxxx/common/resources`
|
||||
*** `rules/Sxxxx/[LANGUAGE]`: contains the language-specific RSPEC. For every rule, there must be at least one `[LANGUAGE]` subdirectory. +
|
||||
`[LANGUAGE]` can be any of the following:
|
||||
|
||||
include::supported_languages.adoc[]
|
||||
|
||||
**** `rules/Sxxxx/[LANGUAGE]/rule.adoc`: asciidoc file used to generate the `Sxxxx` rule description for programming language `[LANGUAGE]`. It can include parts from `*.adoc` files located in the parent directory.
|
||||
**** `rules/Sxxxx/[LANGUAGE]/metadata.json`: metadatas for the specific language. Each key at the top will completely override the key of the `metadata.json` file of the parent directory.
|
||||
* https://github.com/SonarSource/rspec/tree/master/shared_content[shared_content] directory: contains content that needs to be shared among multiple rules. Subfolders are currently not standardized.
|
||||
|
||||
== Rule description file
|
||||
|
||||
Refer to the <<docs/description.adoc#,documentation about rules description>> for the content of the rule.adoc file.
|
||||
|
||||
== Metadata file
|
||||
|
||||
Refer to the <<docs/metadata.adoc#,documentation about the metadata file>> for the content of the metadata.json file.
|
||||
|
||||
== Non-content modifications
|
||||
|
||||
Refer to the <<docs/testing.adoc#,documentation about testing>> if you plan to change something else than rules.
|
||||
|
||||
== Search rules
|
||||
|
||||
All specified rules (implemented or not) are listed in the https://sonarsource.github.io/rspec/#/[Search Page].
|
||||
|
||||
For each rule, you can find the languages it covers, the descriptions for each language as well as the related open tickets and pull requests. +
|
||||
There will be a red warning with a link to the rule pull request if the rule has not been implemented yet (i.e. is not present on the main branch yet).
|
||||
|
||||
You can also use GitHub search among unmerged PRs https://github.com/SonarSource/rspec/pulls[here].
|
||||
|
||||
WARNING: Unlike the Search Page, the GitHub search across the PRs for unimplemented rules considers only the PR summary and title. It does not search the content of the proposed rule (i.e. it does not look into `rule.adoc` nor into `metadata.json`).
|
||||
|
||||
[[AddModifyRule]]
|
||||
== Create or modify a rule
|
||||
|
||||
Before, the Languages Team used Jira to host both implemented and unimplemented rules. This is why the `rules` directory contains both too.
|
||||
|
||||
However, one of the reasons we are migrating to a git repository is that we want to have a clean process and history for rule creation and modification.
|
||||
|
||||
In particular, the main branch aims at representing what will be integrated in the next version of the analyzers, i.e. what will be part of the next releases.
|
||||
|
||||
Thus every newly created rule or modification of rule should follow these steps:
|
||||
|
||||
. Create a pull request adding or modifying a rule
|
||||
. Ask for a review
|
||||
. Create an implementation ticket
|
||||
. Implement the new rule or the change in the existing rule
|
||||
. Fetch the updated metadata with `rule-api` by pointing it to the PR branch with `-branch` parameter
|
||||
. Merge the implementation PR
|
||||
. Merge the RSPEC PR
|
||||
|
||||
A <<multi-language-rule-creation>> is somewhat more involved.
|
||||
|
||||
=== 1. Create a pull request
|
||||
|
||||
==== To create a new rule
|
||||
. go to the https://github.com/SonarSource/rspec/actions/workflows/create_new_rspec.yml[Create new RSPEC] GitHub action
|
||||
. click on the grey _Run wokflow_ button (on the right).
|
||||
. in the field _"Comma-separated list of targeted languages"_ write the list of languages you want to specify this rule for. +
|
||||
They can be any of the following:
|
||||
|
||||
include::supported_languages.adoc[]
|
||||
|
||||
. click on the green _Run workflow_ button.
|
||||
|
||||
image::img/new-rule-workflow.png[]
|
||||
|
||||
You should see https://github.com/pulls/assigned[a new pull request assigned to you] with the appropriate language labels.
|
||||
It might take up to a few minutes to appear.
|
||||
It contains a scaffolding of files for the new rule. Feel free to modify it as you please.
|
||||
|
||||
The title of the PR for a new rule will say only "Create rule Sxxxx", which is not very informative. +
|
||||
Modify the title to better summarize the nature or the rule, so that it is easier to find when searching through unimplemented rules.
|
||||
Do preserve the "Create rule Sxxxx" prefix, as it is used by our tooling.
|
||||
For example:
|
||||
|
||||
----
|
||||
Create rule S7028: All identifiers should be in CamelCase
|
||||
----
|
||||
|
||||
Add the description of the PR to further increase its discoverability
|
||||
(GitHub PR search does not see the `rule.adoc`).
|
||||
To do that, click on the three dots (next to the smile) on the first comment (created by github-actions bot) and select "Edit".
|
||||
|
||||
The rule must contain subdirectories corresponding to all the languages this rule will be implemented for.
|
||||
|
||||
Each language subdirectory contains the `rule.adoc` that is the root document used to render the specification.
|
||||
The rule specification in `rule.adoc` can include other `*.adoc` files that are in the language subdirectory or in the parent directory by using the `include::content.adoc[]` syntax.
|
||||
|
||||
To reduce the number of turnarounds with the asciidoc edits you can install an asciidoc plugin.
|
||||
Otherwise, you can use https://asciidoclive.com/[AsciiDocLIVE] and this https://docs.asciidoctor.org/asciidoc/latest/syntax-quick-reference/[cheatsheet].
|
||||
|
||||
==== To add language to an existing rule
|
||||
|
||||
Similar to adding a new rule, if the rule exists on the main branch, trigger the "Add language to a rule" GitHub action.
|
||||
|
||||
. go to the https://github.com/SonarSource/rspec/actions/workflows/add_language.yml[Add language to a rule] GitHub action
|
||||
. click on the grey _Run workflow_ button (on the right).
|
||||
. in the field _"ID of an existing rule"_ write the ID of the existing rule you want to add language to.
|
||||
It must be in a form `Sxxxx` where `xxxx` is the number of the rule.
|
||||
For example: `S100`, `S1234`, or `S6000`.
|
||||
. in the field _"Language to be added to the rule"_ specify one language you want to add to the rule. +
|
||||
It can be any of the following:
|
||||
|
||||
include::supported_languages.adoc[]
|
||||
|
||||
. click on the green _Run workflow_ button.
|
||||
|
||||
image::img/add-language-workflow.png[]
|
||||
|
||||
You should see https://github.com/pulls/assigned[a new pull request assigned to you] with the appropriate language label.
|
||||
It might take up to a few minutes to appear.
|
||||
It contains a scaffolding of files for the new rule. Feel free to modify it as you please.
|
||||
|
||||
The title of the PR for a new rule will say only "Create rule Sxxxx", which is not very informative. +
|
||||
Modify the title to better summarize the nature or the rule, so that it is easier to find when searching through unimplemented rules.
|
||||
Do preserve the "Create rule Sxxxx" prefix, as it is used by our tooling.
|
||||
For example:
|
||||
----
|
||||
Create rule S100: Method names should comply with a naming convention
|
||||
----
|
||||
|
||||
Otherwise, if the rule has not been merged yet (i.e. the rule has not been implemented by any plugin), you can use the already existing PR corresponding to this rule. +
|
||||
|
||||
==== To modify an existing rule
|
||||
Create a branch for your modifications manually.
|
||||
Then open a pull request manually.
|
||||
The subject must start with the following required prefix:
|
||||
----
|
||||
Modify rule Sxxxx
|
||||
----
|
||||
Add a short summary of the PR after the required prefix. For example:
|
||||
----
|
||||
Modify rule S1234: Allow tail recursion for languages supporting TCO
|
||||
----
|
||||
Add at least one label corresponding to the language(s) the PR relates to.
|
||||
|
||||
==== To deprecate a rule
|
||||
Create a branch for the deprecation manually.
|
||||
Then open a pull request manually.
|
||||
In the subject add the following text:
|
||||
----
|
||||
Deprecate rule Sxxxx
|
||||
----
|
||||
Add at least one label corresponding to the language(s) the PR relates to.
|
||||
|
||||
In the `metadata.json` of the rule you want to deprecate:
|
||||
|
||||
* Remove all tags
|
||||
* Remove all quality profiles
|
||||
* Change the status to `deprecated`
|
||||
* Fill in the `replacementRules` array with strings in the form `"RSPEC-xxxx"` with the rules that deprecate this one, if any
|
||||
|
||||
See link:rules/S1212/metadata.json[S1212] for an example.
|
||||
|
||||
==== To delete a rule
|
||||
If the rule has never been implemented and is still defined in an open pull request, just close the pull request. +
|
||||
Otherwise, create a pull request to change the status of the rule to "closed".
|
||||
|
||||
NOTE: use a GitHub hot-key `t` in the source view to navigate to an existing rule specification.
|
||||
|
||||
=== 2. Ask for a review
|
||||
|
||||
Every PR that is creating or modifying a rule should be reviewed.
|
||||
Exceptions can be made for small PRs that only resolve simple spelling mistakes.
|
||||
|
||||
If it is a new rule, or if it requires the analyzer to change its implementation, do not merge the pull request yet.
|
||||
|
||||
If the change does not require an implementation, merge the pull request after the review.
|
||||
|
||||
Following the submission of every PR, an automatic checklist will be generated.
|
||||
This checklist is intended for the reviewer and serves as a guide to ensure comprehensive evaluation of the PR.
|
||||
As the reviewer navigates through the review process, each fulfilled item on the checklist should be marked off.
|
||||
|
||||
A PR should not be merged before the checklist is fully completed.
|
||||
This ensures that all necessary checks have been made, and all requirements have been met before merging, fostering a systematic and thorough review process.
|
||||
|
||||
=== 3. Create an implementation ticket
|
||||
|
||||
Create an implementation ticket as it is usually done for your plugin (i.e. as a Jira ticket or a Github issue). +
|
||||
For this ticket to be correcty indexed on the search page of the rules, it has to contain the rule ID (RSPEC-1234 or S1234) either in the ticket title or in the ticket description. +
|
||||
It is also recommended to add a link to the Github Page of the related rule, to ease the navigation between the ticket and the rule.
|
||||
|
||||
In the pull request adding the rule specification, add the following text referencing the implementation ticket:
|
||||
----
|
||||
Implementation ticket: CPP-1234 (for a CFamily ticket on Jira)
|
||||
----
|
||||
or
|
||||
----
|
||||
Implementation ticket: SonarSource/sonar-dotnet/issues/1234 (for a sonar-dotnet issue on Github)
|
||||
----
|
||||
|
||||
|
||||
=== 4. Implement the rule
|
||||
|
||||
Implement the rule or the modification as usual, generate the rule metadata,
|
||||
and merge the rule implementation in your analyzer repository.
|
||||
|
||||
|
||||
==== Generate rule metadata for the analyzer
|
||||
|
||||
* Download the last version of https://github.com/SonarSource/sonar-rule-api[rule-api].
|
||||
* Run `generate` and specify the RSPEC-repository branch with the modified version of the rule specification.
|
||||
|
||||
Example:
|
||||
[source,shell]
|
||||
----
|
||||
$ java -jar rule-api-2.1.0.jar generate -branch <RSPEC branch> -rule S4328
|
||||
----
|
||||
|
||||
=== 5. Merge the RSPEC PR
|
||||
|
||||
Once the corresponding implementation is done you can merge the PR containing the new rule
|
||||
(or the new version of the existing rule).
|
||||
|
||||
=== Multi-Language Rule Creation
|
||||
Multi-language rule creation has more steps than the default process because it involves multiple roles that typically do not coincide.
|
||||
It is infeasible to synchronize the implementation of the rule for all the languages it covers.
|
||||
|
||||
. An RSPECator creates a PR and specifies the multi-language rule.
|
||||
* The RSPECator asks for a review for the PR.
|
||||
* The RSPECator does not merge the PR, even after the review is done.
|
||||
. The RSPECator opens implementation tickets for all the targeted languages.
|
||||
. An Ada analyzer developer Alice implements the rule first. Alice prepares the PR with the implementation.
|
||||
. As soon as the implementation of the rule is ready for Ada analyzer, Alice merges both PRs:
|
||||
.. Alice fetches the rule metadata with `rule-api` into Ada analyzer. She needs to specify the PR branch in the `-branch` argument of `rule-api`.
|
||||
.. Alice merges the rule implementation in Ada analyzer.
|
||||
.. Alice merges the RSPEC PR opened by the RSPECator.
|
||||
. A Cobol analyzer developer Bob implements the rule some time later. Bob prepares the PR with the implementation.
|
||||
. As soon as the Cobol implementation is ready:
|
||||
.. If Bob needs to change the rule specification, he opens an RSPEC PR and uses the PR branch in the `-branch` argument of `rule-api`.
|
||||
.. Bob fetches the rule metadata with `rule-api` into Cobol analyzer.
|
||||
.. Bob merges the rule implementation in Cobol analyzer.
|
||||
.. If Bob had opened an RSPEC PR with changes, he merges it once the rule is implemented in the analyzer.
|
||||
|
||||
== Untriaged Pull Requests
|
||||
|
||||
Untriaged PRs are the ones without any GitHub label assigned to them.
|
||||
You can easily see all of them with the filter https://github.com/SonarSource/rspec/pulls?q=is%3Aopen+is%3Apr+no%3Alabel[`no:label`].
|
||||
|
||||
All triaged PRs should have at least one label that corresponds to the bubble(s) the PR is related to.
|
||||
This allows bubbles to easily filter the PRs they are interested in.
|
||||
|
||||
== Tooling
|
||||
https://github.com/SonarSource/rspec/tree/master/rspec-tools[rspec-tools]::
|
||||
A python CLI tool for adding and validating rules. It is used by GitHub checks and GitHub actions.
|
||||
For more information see the README file in the `rspec-tools` directory.
|
||||
https://github.com/SonarSource/rspec/tree/master/frontend[frontend]::
|
||||
The GitHub page that enables the search for rules.
|
||||
For more information see the README file in the `frontend` directory.
|
||||
|
||||
== RSPEC dataflow
|
||||
|
||||
The following graph shows the path of an RSPEC from its inception in Github RSPEC repository to its consumption in SQ/SC/SL or on rules.sonarsource.com: +
|
||||
(The part that is grayed out corresponds to what existed before, when RSPECs were hosted in Jira)
|
||||
|
||||
image::img/RSPEC-flow-2.png[]
|
||||
|
||||
== Help
|
||||
|
||||
Tickets related to this RSPEC repository are in Jira, in the https://jira.sonarsource.com/projects/RULEAPI/issues/RULEAPI-324?filter=allopenissues[RULEAPI] project.
|
||||
|
||||
You found a bug, something is bothering you or you have an idea of how to improve the project? First, have a look at all the https://jira.sonarsource.com/projects/RULEAPI/issues/RULEAPI-324?filter=allopenissues[open tickets]. If you don't see anything related to your subject, please open a new ticket in the backlog, with `backlog` as the fix version.
|
@ -1,17 +0,0 @@
|
||||
FROM public.ecr.aws/docker/library/python:3.9-slim-buster
|
||||
|
||||
# Also install NodeJS 16 to run Sonar analysis
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends jq php-json-schema asciidoctor pipenv git curl \
|
||||
ca-certificates gnupg && \
|
||||
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | \
|
||||
gpg --dearmor -o /etc/apt/nodesource-keyring.gpg && \
|
||||
echo "deb [signed-by=/etc/apt/nodesource-keyring.gpg] https://deb.nodesource.com/node_20.x nodistro main" \
|
||||
> /etc/apt/sources.list.d/nodesource.list && \
|
||||
apt-get update && \
|
||||
apt-get -y install nodejs && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
CMD ["bash"]
|
@ -1,165 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
# Based on asciidoctor main ruby script.
|
||||
# This is only meant to introspect and log which asciidoc files were used.
|
||||
|
||||
require 'asciidoctor'
|
||||
require 'asciidoctor/cli'
|
||||
|
||||
MAIN_FILE_REGEX = /^.*\/rules\/(?<id>S\d+)\/(?:(?<lang>\w+)\/)?rule.adoc$/
|
||||
|
||||
class MainFileLogger < Asciidoctor::Extensions::Preprocessor
|
||||
include Asciidoctor::Logging
|
||||
|
||||
def process document, reader
|
||||
# Enable sourcemap to track source location.
|
||||
# This is useful to report more accurate errors in other loggers.
|
||||
document.sourcemap = true
|
||||
|
||||
main_file = document.normalize_system_path(reader.file, document.reader.dir)
|
||||
|
||||
# This assumes unix-style path separator.
|
||||
if nil == main_file.match(MAIN_FILE_REGEX)
|
||||
abort("Main file does not follow expected pattern: #{main_file}")
|
||||
end
|
||||
|
||||
logger.info("ASCIIDOC LOGGER MAIN FILE:#{main_file}")
|
||||
reader
|
||||
end
|
||||
end
|
||||
|
||||
class IncludeLogger < Asciidoctor::Extensions::IncludeProcessor
|
||||
include Asciidoctor::Logging
|
||||
|
||||
def initialize document
|
||||
@config = {} # Defined in parent class; will be updated by the extension registry mechanism.
|
||||
@document = document
|
||||
|
||||
# @document.reader.file is not defined yet at this stage.
|
||||
# Therefore we cannot compute the main file path and cache it.
|
||||
# This cannot be done once in handles? because the object is then frozen.
|
||||
# For these reasons, we end up recomputing the rule directory path each time.
|
||||
end
|
||||
|
||||
def get_main_file reader
|
||||
# See how include_stack is used:
|
||||
# https://github.com/asciidoctor/asciidoctor/blob/f3800cc9c92faf8370041b2b27a61124318ed289/lib/asciidoctor/reader.rb#L669
|
||||
if reader.include_stack.empty?
|
||||
reader.file
|
||||
else
|
||||
main_frame = reader.include_stack.fetch(0)
|
||||
main_frame.fetch(1)
|
||||
end
|
||||
end
|
||||
|
||||
def handles? target
|
||||
include_path = @document.normalize_system_path(target, @document.reader.dir)
|
||||
|
||||
main_file = get_main_file(@document.reader)
|
||||
main_file = @document.normalize_system_path(main_file, @document.reader.dir)
|
||||
rule_dir = File.dirname(File.dirname(main_file))
|
||||
rule_id = File.basename(rule_dir)
|
||||
if rule_id == 'rules'
|
||||
# This is a language-agnostic rule description.
|
||||
rule_dir = File.dirname(main_file)
|
||||
rule_id = File.basename(rule_dir)
|
||||
end
|
||||
git_dir = File.dirname(File.dirname(rule_dir))
|
||||
shared_dir = File.join(git_dir, 'shared_content')
|
||||
|
||||
rule_dir = rule_dir + '/' # Don't allow S100 to include things from S1000.
|
||||
if !include_path.start_with?(rule_dir) && !include_path.start_with?(shared_dir)
|
||||
logger.info("ASCIIDOC LOGGER CROSSREFERENCE:#{rule_id} cross-references #{include_path}")
|
||||
end
|
||||
|
||||
logger.info("ASCIIDOC LOGGER INCLUDE:#{include_path}")
|
||||
|
||||
false # Actually, this include processor does nothing.
|
||||
end
|
||||
|
||||
# Intentionnaly no process function here.
|
||||
end
|
||||
|
||||
class SourceLogger < Asciidoctor::Extensions::TreeProcessor
|
||||
include Asciidoctor::Logging
|
||||
|
||||
def get_source_location block
|
||||
loc = block.source_location # Asciidoctor::Reader::Cursor.
|
||||
return "#{loc.file}:#{loc.lineno}"
|
||||
end
|
||||
|
||||
def get_rule document
|
||||
main_file = document.normalize_system_path(document.reader.file, document.reader.dir)
|
||||
res = main_file.match(MAIN_FILE_REGEX)
|
||||
lang = res[:lang] || 'default'
|
||||
"#{res[:id]}/#{lang}"
|
||||
end
|
||||
|
||||
def process document
|
||||
rule = get_rule(document)
|
||||
source_blocks = document.find_by(context: :listing, style: 'source')
|
||||
|
||||
# Collect individually valid blocks per diff-id.
|
||||
blocks_per_id = Hash.new { |hash, key| hash[key] = Array.new }
|
||||
|
||||
# Find blocks with only diff-id but no diff-type, or vice-versa, or invalid diff-type.
|
||||
source_blocks.each do |block|
|
||||
id = block.attr('diff-id', nil)
|
||||
type = block.attr('diff-type', nil)
|
||||
|
||||
if !id and !type
|
||||
next # Nothing to validate
|
||||
end
|
||||
|
||||
loc = get_source_location(block)
|
||||
|
||||
if !id
|
||||
logger.info("ASCIIDOC LOGGER DIFF:[#{rule}] diff-id is missing in #{loc}")
|
||||
next
|
||||
end
|
||||
|
||||
if !type
|
||||
logger.info("ASCIIDOC LOGGER DIFF:[#{rule}] diff-type is missing in #{loc}")
|
||||
next
|
||||
elsif !['compliant', 'noncompliant'].include?(type)
|
||||
logger.info("ASCIIDOC LOGGER DIFF:[#{rule}] diff-type '#{type}' is not valid in #{loc}")
|
||||
next
|
||||
end
|
||||
|
||||
# The block is valid on its own.
|
||||
blocks_per_id[id].push(block)
|
||||
end
|
||||
|
||||
# Each diff-id should have:
|
||||
# * exactly 1 noncompliant block, and
|
||||
# * 1 or more compliant blocks.
|
||||
# Find blocks that break this rule.
|
||||
blocks_per_id.each do |id, blocks|
|
||||
# Sort to ensure deterministic output.
|
||||
blocks.sort_by! { |block| get_source_location(block) }
|
||||
|
||||
locs = blocks.map { |block| get_source_location(block) }.join(', ')
|
||||
|
||||
compliant = blocks.count { |block| block.attr('diff-type') == 'compliant' }
|
||||
if compliant == 0
|
||||
logger.info("ASCIIDOC LOGGER DIFF:[#{rule}] diff-id=#{id} has zero compliant example: #{locs}")
|
||||
end
|
||||
|
||||
noncompliant = blocks.count { |block| block.attr('diff-type') == 'noncompliant' }
|
||||
if noncompliant != 1
|
||||
message = noncompliant == 0 ? "zero noncompliant example" : "too many noncompliant examples"
|
||||
logger.info("ASCIIDOC LOGGER DIFF:[#{rule}] diff-id=#{id} has #{message}: #{locs}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Asciidoctor::Extensions.register do
|
||||
preprocessor MainFileLogger
|
||||
include_processor IncludeLogger.new @document
|
||||
treeprocessor SourceLogger
|
||||
end
|
||||
|
||||
invoker = Asciidoctor::Cli::Invoker.new ARGV
|
||||
GC.start
|
||||
invoker.invoke!
|
||||
exit invoker.code
|
@ -1,86 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Validate file inclusion and cross-references.
|
||||
#
|
||||
# This script is parametrized by this environment variable:
|
||||
# * TOPLEVEL: $TOPLEVEL/rules and $TOPLEVEL/shared_content will be validated.
|
||||
#
|
||||
# The script exists with a non-zero value when an unexpected error happened or
|
||||
# there are asciidoc validation errors.
|
||||
#
|
||||
# Validation errors are reported on stderr.
|
||||
#
|
||||
# Implementation details:
|
||||
#
|
||||
# The validation of file inclusion and cross-references needs to be done
|
||||
# on all rule descriptions, including the default, language-agnostic
|
||||
# description, with rspecator-view. Otherwise, a rule could drop an include
|
||||
# of a shared_content asciidoc and that file could become unused.
|
||||
#
|
||||
# We use a custom asciidoctor with extra logging for this purpose.
|
||||
# The format for the interesting log entries are:
|
||||
# asciidoctor: INFO: ASCIIDOC LOGGER MAIN FILE: $PATH
|
||||
# asciidoctor: INFO: ASCIIDOC LOGGER INCLUDED: $PATH
|
||||
# asciidoctor: INFO: ASCIIDOC LOGGER CROSSREFERENCE: $RULEID cross-references $PATH
|
||||
# asciidoctor: INFO: ASCIIDOC LOGGER DIFF: $VALIDATION_FAILURE_MESSAGE
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)"
|
||||
TOPLEVEL="$(realpath "${TOPLEVEL}")"
|
||||
RULES_DIR="${TOPLEVEL}/rules"
|
||||
SHARED_CONTENT_DIR="${TOPLEVEL}/shared_content"
|
||||
TMPOUT_DIR="$(mktemp -d)"
|
||||
|
||||
exit_code=0
|
||||
|
||||
grep_nofail() {
|
||||
# Grep but always exit with 0 when there are no matches.
|
||||
# Yet, exit with non-zero if an error occured.
|
||||
grep "$@" || [ "$?" == "1" ]
|
||||
}
|
||||
|
||||
extract_messages_from_log() {
|
||||
# The first 3 columns of the log are not relevant.
|
||||
# The 4th (and any that follows if ':' is used in the message)
|
||||
# provides the relevant validation error message.
|
||||
cut -d ':' -f 4- | sort -u
|
||||
}
|
||||
|
||||
find "${RULES_DIR}" -name 'rule.adoc' \
|
||||
| xargs "${SCRIPT_DIR}/custom-asciidoctor" -a rspecator-view --verbose -R "${RULES_DIR}" -D "${TMPOUT_DIR}" 2>&1 \
|
||||
| grep_nofail -e 'ASCIIDOC LOGGER' \
|
||||
> "${TMPOUT_DIR}/asciidoc_introspection"
|
||||
|
||||
cross_references=$(grep_nofail -e 'CROSSREFERENCE' "${TMPOUT_DIR}/asciidoc_introspection" | extract_messages_from_log)
|
||||
if [[ -n "$cross_references" ]]; then
|
||||
echo >&2 'ERROR: Some rules try to include content from unallowed directories.'
|
||||
echo >&2 'To share content between rules, you should use the "shared_content" folder at the root of the repository.'
|
||||
echo >&2 'List of errors:'
|
||||
echo >&2 "${cross_references}"
|
||||
exit_code=1
|
||||
fi
|
||||
|
||||
grep_nofail -Pe '(INCLUDE|MAIN FILE)' "${TMPOUT_DIR}/asciidoc_introspection" \
|
||||
| extract_messages_from_log \
|
||||
> "${TMPOUT_DIR}/used_asciidoc_files"
|
||||
|
||||
git ls-files --cached -- "${RULES_DIR}/**.adoc" "${SHARED_CONTENT_DIR}/**.adoc" \
|
||||
| xargs realpath \
|
||||
> "${TMPOUT_DIR}/all_asciidoc_files"
|
||||
|
||||
orphans=$(comm -1 -3 <(sort -u "${TMPOUT_DIR}/used_asciidoc_files") <(sort -u "${TMPOUT_DIR}/all_asciidoc_files"))
|
||||
if [[ -n "$orphans" ]]
|
||||
then
|
||||
printf >&2 'ERROR: These adoc files are not included anywhere:\n-----\n%s\n-----\n' "$orphans"
|
||||
exit_code=1
|
||||
fi
|
||||
|
||||
bad_diffs=$(grep_nofail -e 'DIFF' "${TMPOUT_DIR}/asciidoc_introspection" | extract_messages_from_log)
|
||||
if [[ -n "$bad_diffs" ]]
|
||||
then
|
||||
printf >&2 'ERROR: Diff highlighting is used incorrectly:\n-----\n%s\n-----\n' "$bad_diffs"
|
||||
exit_code=1
|
||||
fi
|
||||
|
||||
exit $exit_code
|
@ -1,43 +0,0 @@
|
||||
#! /bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ACTION=${1}
|
||||
CACHE_NAME=${2}
|
||||
PATH_TO_CACHE=${3}
|
||||
|
||||
CACHE_URL="http://${CIRRUS_HTTP_CACHE_HOST}/${CACHE_NAME}"
|
||||
|
||||
TMP_PATH="/tmp/tmp-cache.tgz"
|
||||
|
||||
case "${ACTION}" in
|
||||
|
||||
download)
|
||||
echo "Download cache with key ${CACHE_NAME} from ${CACHE_URL}"
|
||||
curl --silent --show-error --fail --location --output "${TMP_PATH}" "${CACHE_URL}" || {
|
||||
echo "Cache download failed" >&2
|
||||
exit 0
|
||||
}
|
||||
du -hs "${TMP_PATH}"
|
||||
tar -Pxzf "${TMP_PATH}"
|
||||
rm "${TMP_PATH}"
|
||||
;;
|
||||
|
||||
upload)
|
||||
echo "Upload cache to ${CACHE_URL}"
|
||||
tar -Pczf "${TMP_PATH}" "${PATH_TO_CACHE}"
|
||||
du -hs "${TMP_PATH}"
|
||||
curl --silent --show-error -X POST --data-binary "@${TMP_PATH}" "${CACHE_URL}" || {
|
||||
echo "Cache upload failed" >&2
|
||||
exit 0
|
||||
}
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Unexpected cache ACTION: ${ACTION}" >&2
|
||||
exit 1
|
||||
;;
|
||||
|
||||
esac
|
||||
|
||||
echo "Cache ${ACTION}ed succeeded."
|
@ -1,7 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# When neither BASE_BRANCH nor DEFAULT_BRANCH are defined, fall back to "master".
|
||||
BRANCH="${BASE_BRANCH:-${DEFAULT_BRANCH:-master}}"
|
||||
git fetch --force origin "refs/heads/${BRANCH}:refs/remotes/origin/${BRANCH}"
|
@ -1,3 +0,0 @@
|
||||
FROM public.ecr.aws/docker/library/node:20.9.0
|
||||
|
||||
CMD ["bash"]
|
@ -1,8 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
mkdir -p out
|
||||
asciidoctor -R rules -D out 'rules/*/*/rule.adoc' -q
|
||||
cd rules
|
||||
find . -name 'metadata.json' -exec cp --parents '{}' ../out \;
|
||||
cd ..
|
@ -1,7 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd rspec-tools
|
||||
pipenv install --dev
|
||||
pipenv run pip install pytest pytest-cov
|
@ -1,149 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -uo pipefail
|
||||
|
||||
# Install script dependencies
|
||||
set -e
|
||||
cd rspec-tools && pipenv install && cd ..
|
||||
set +e
|
||||
|
||||
# This script runs all tests; it doesn't exit at the first failure.
|
||||
exit_code=0
|
||||
|
||||
readonly ALLOWED_RULE_SUB_FOLDERS=['common'];
|
||||
|
||||
# Validate user-visible rule descriptions
|
||||
# i.e., without rspecator-view.
|
||||
./ci/generate_html.sh
|
||||
cd rspec-tools
|
||||
if pipenv run rspec-tools check-description --d ../out; then
|
||||
echo "Rule descriptions are fine"
|
||||
else
|
||||
echo "ERROR: There are invalid rule descriptions"
|
||||
exit_code=1
|
||||
fi
|
||||
cd ..
|
||||
|
||||
# Compute the set of affected rules
|
||||
git fetch origin "$CIRRUS_DEFAULT_BRANCH"
|
||||
branch_base_sha=$(git merge-base FETCH_HEAD HEAD)
|
||||
echo "Comparing against the merge-base: $branch_base_sha"
|
||||
changeset=$(git diff --name-only "$branch_base_sha"..HEAD)
|
||||
affected_rules=$(printf '%s\n' "$changeset" | grep '/S[0-9]\+/' | sed 's:\(.*/S[0-9]\+\)/.*:\1:' | sort | uniq)
|
||||
if printf '%s\n' "$changeset" | grep -qv '/S[0-9]\+/'; then
|
||||
echo "Some rpec tools or shared_content changed, validating all rules"
|
||||
affected_rules=rules/*
|
||||
fi
|
||||
|
||||
# Validate some properties of the asciidoc:
|
||||
#
|
||||
# [properties validated only on affected rules]
|
||||
# * Rules should have at least one language specification,
|
||||
# unless they are closed or deprecated.
|
||||
# * The include:: should have an empty line before and after them.
|
||||
# * Only valid languages can be used as subdirectories in rule directories,
|
||||
# with the exception of ALLOWED_RULE_SUB_FOLDERS.
|
||||
# * Asciidoc files are free or errors and warnings.
|
||||
# * ifdef/endif are used appropriatedly.
|
||||
#
|
||||
# [properties validated always on all rules]
|
||||
# * Rule descriptions can include other asciidoc files from the same rule
|
||||
# directory or from shared_content.
|
||||
# * All asciidoc files are used/included.
|
||||
|
||||
echo "Testing the following rules: ${affected_rules}"
|
||||
supportedLanguages=$(sed 's/ or//' supported_languages.adoc | tr -d '`,')
|
||||
for dir in $affected_rules
|
||||
do
|
||||
if [ ! -d "$dir" ]; then
|
||||
echo "Apparently $dir is deleted, skipping"
|
||||
continue
|
||||
fi
|
||||
dir=${dir%*/}
|
||||
|
||||
# check if there are language specializations
|
||||
subdircount=$(find "$dir" -maxdepth 1 -mindepth 1 -type d | wc -l)
|
||||
if [[ "$subdircount" -eq 0 ]]
|
||||
then
|
||||
# no specializations, that's fine if the rule is deprecated
|
||||
if grep -Pq '"status": "(deprecated|closed)"' "$dir/metadata.json"; then
|
||||
echo "INFO: deprecated generic rule $dir with no language specializations"
|
||||
else
|
||||
echo "ERROR: non-deprecated generic rule $dir with no language specializations"
|
||||
exit_code=1
|
||||
fi
|
||||
else
|
||||
# Add the full path of all adoc files that were affected for sanitization
|
||||
find ~+/"${dir}" -name '*.adoc' >> all_asciidocs
|
||||
|
||||
for language in "${dir}"/*/
|
||||
do
|
||||
language=${language%*/}
|
||||
if [[ ! "${supportedLanguages[*]}" == *"${language##*/}"* ]]; then
|
||||
if [[ ! "${ALLOWED_RULE_SUB_FOLDERS[*]}" == *"${language##*/}"* ]]; then
|
||||
echo "ERROR: ${language##*/} is not a supported language"
|
||||
exit_code=1
|
||||
fi
|
||||
else
|
||||
RULE="$language/rule.adoc"
|
||||
if test -f "$RULE"; then
|
||||
# Errors emitted by asciidoctor don't include the full path.
|
||||
# https://github.com/asciidoctor/asciidoctor/issues/3414
|
||||
# To ease debugging, we copy the rule.adoc into tmp_SXYZ_language.adoc
|
||||
# and run asciidoctor on them instead.
|
||||
# We add the implicit header "Description" to prevent an asciidoctor warning.
|
||||
TMP_ADOC="$language/tmp_$(basename "${dir}")_${language##*/}.adoc"
|
||||
echo "== Description" > "$TMP_ADOC"
|
||||
cat "$RULE" >> "$TMP_ADOC"
|
||||
else
|
||||
echo "ERROR: no asciidoc file $RULE"
|
||||
exit_code=1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
cd rspec-tools
|
||||
cat ../all_asciidocs | xargs pipenv run rspec-tools check-asciidoc >validate_asciidoc 2>&1
|
||||
if [ -s validate_asciidoc ]; then
|
||||
echo "ERROR: Invalid asciidoc description:"
|
||||
cat validate_asciidoc
|
||||
exit_code=1
|
||||
fi
|
||||
rm -f validate_asciidoc ../all_asciidocs
|
||||
cd ..
|
||||
|
||||
|
||||
|
||||
# Run asciidoctor and fail if a warning is emitted.
|
||||
# Use the tmp_SXYZ_language.adoc files (see note above).
|
||||
ADOC_COUNT=$(find rules -name "tmp*.adoc" | wc -l)
|
||||
if (( ADOC_COUNT > 0 )); then
|
||||
if asciidoctor --failure-level=WARNING -o /dev/null rules/*/*/tmp*.adoc; then
|
||||
if asciidoctor -a rspecator-view --failure-level=WARNING -o /dev/null rules/*/*/tmp*.adoc; then
|
||||
echo "${ADOC_COUNT} documents checked with success"
|
||||
else
|
||||
echo "ERROR: malformed asciidoc files in rspecator-view"
|
||||
exit_code=1
|
||||
fi
|
||||
else
|
||||
echo "ERROR: malformed asciidoc files"
|
||||
exit_code=1
|
||||
fi
|
||||
else
|
||||
echo "No new asciidoc file changed"
|
||||
fi
|
||||
find rules -name "tmp*.adoc" -delete
|
||||
|
||||
# Validate file inclusion, cross-references, and other properties.
|
||||
#
|
||||
# This part of the validation is extracted in a separate script,
|
||||
# which is covered by tests unlike what is above this line.
|
||||
TOPLEVEL=. ./ci/asciidoc_validation/validate.sh || exit_code=1
|
||||
|
||||
if (( exit_code == 0 )); then
|
||||
echo "Success"
|
||||
else
|
||||
echo "There were errors"
|
||||
fi
|
||||
exit $exit_code
|
@ -1,25 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Validates that there are no files with .cs, .vb, .razor or .cshtml extensions present inside rules folder.
|
||||
#
|
||||
# As part of the new DotNet squad rule specification sprint guidelines, test case files similar to the ones
|
||||
# used for unit tests in sonar-dotnet should be temporarely added under the rule folder on RSPEC repository.
|
||||
# Those files can be of any number for both C# (*.cs, *.razor, *.cshtml) and VB.NET (.*vb).
|
||||
# The test case files will be copied to the sonar-dotnet repository during the initial phases of implementation
|
||||
# and will serve as an initial test bed.
|
||||
# Before merging the PR on the RSPEC side, it is important to ensure that these test case files are deleted.
|
||||
# The script make sure to fail the CI if any of those previously mentioned files are present inside the rules folder.
|
||||
set -euxo pipefail
|
||||
|
||||
TOPLEVEL="$(realpath .)"
|
||||
RULES_DIR="${TOPLEVEL}/rules"
|
||||
CSVB_FILES=($(find "${RULES_DIR}" -type f -name "*.cs" -o -name "*.vb" -o -name "*.razor" -o -name "*.cshtml"))
|
||||
|
||||
if [ ${#CSVB_FILES[@]} -gt 0 ]; then
|
||||
echo "ERROR: '.cs','.vb','.razor' or '.cshtml' files are detected."
|
||||
printf '%s\n' "${CSVB_FILES[@]}"
|
||||
exit 1
|
||||
else
|
||||
echo "SUCCESS: no '.cs' or '.vb' files detected."
|
||||
exit 0
|
||||
fi
|
@ -1,24 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -euxo pipefail
|
||||
|
||||
CACHE_PATH=$1
|
||||
echo "CACHE_PATH: $CACHE_PATH"
|
||||
[ ! -d $CACHE_PATH ] && mkdir $CACHE_PATH
|
||||
ls -al $CACHE_PATH
|
||||
|
||||
[ -f "$CACHE_PATH/link_probes.history" ] && cp "$CACHE_PATH/link_probes.history" ./rspec-tools/
|
||||
|
||||
./ci/generate_html.sh
|
||||
|
||||
# validate the links in asciidoc
|
||||
cd rspec-tools
|
||||
if pipenv install && pipenv run rspec-tools check-links --d ../out ; then
|
||||
EXIT_CODE=0
|
||||
else
|
||||
EXIT_CODE=1
|
||||
fi
|
||||
cd ..
|
||||
|
||||
cp ./rspec-tools/link_probes.history "$CACHE_PATH/"
|
||||
|
||||
exit $EXIT_CODE
|
@ -1,33 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -ueo pipefail
|
||||
shopt -s lastpipe # To pipe command result into mapfile and have the array variable available in the main shell process.
|
||||
|
||||
git fetch --quiet "${CIRRUS_DEFAULT_ORIGIN:-origin}" "${CIRRUS_DEFAULT_BRANCH:-master}"
|
||||
base="$(git merge-base FETCH_HEAD HEAD)"
|
||||
echo "Comparing against the merge-base: ${base}"
|
||||
if ! git diff --name-only --exit-code "${base}" -- rspec-tools/
|
||||
then
|
||||
basename --multiple rules/* | mapfile -t affected_rules
|
||||
echo "Change in the tools, revalidating all rules"
|
||||
else
|
||||
git diff --name-only "${base}" -- rules/ | # Get all the changes in rules
|
||||
sed -Ee 's#(rules/S[0-9]+)/.*#\1#' | # extract the rule directories
|
||||
sort -u | # deduplicate
|
||||
while IFS= read -r rule; do if [[ -d "$rule" ]]; then echo "$rule"; fi done | # filter out deleted rules
|
||||
sed 's#rules/##' | # get rule ids
|
||||
mapfile -t affected_rules # store them in the `affected_rules` array
|
||||
echo "Validating ${affected_rules[*]}"
|
||||
fi
|
||||
|
||||
printf '\n\n\n'
|
||||
|
||||
# Validate metadata
|
||||
if [[ "${#affected_rules[@]}" -gt 0 ]]
|
||||
then
|
||||
cd rspec-tools
|
||||
pipenv install
|
||||
printf '\n\n\n'
|
||||
pipenv run rspec-tools validate-rules-metadata "${affected_rules[@]}"
|
||||
else
|
||||
echo "No rule changed or added"
|
||||
fi
|
@ -1,99 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Run integration tests for ci/asciidoc_validation.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
# We could write complex checks to ensure only specific commands fail and emit
|
||||
# a specific error message. Instead, we rely on `set -xe` to consistently and
|
||||
# reliably exit with non-zero if any command fails and pinpoint which command
|
||||
# failed in the trace output. We also use a trap on ERR to give users a short
|
||||
# hint, and `set -E` to propagate this trap to shell functions and subshells.
|
||||
#
|
||||
# This allows use to write tests as simple commands, such as
|
||||
# test -f file_exists
|
||||
#
|
||||
# When we want to ensure a command fails, we use this pattern:
|
||||
# { ! command; }
|
||||
set -xeE
|
||||
err_trap() {
|
||||
set +x # Disable tracing when displaying stackframe.
|
||||
echo "Some test failed; look at the trace for more. Here is the stackframe:" >&2
|
||||
i=0
|
||||
while caller $i >&2
|
||||
do
|
||||
(( i++ )) || :
|
||||
done
|
||||
}
|
||||
trap err_trap ERR
|
||||
|
||||
# Ensure the script we test exists and is executable.
|
||||
GIT_TOPLEVEL_DIR="$(git rev-parse --show-toplevel)"
|
||||
VALIDATE_SCRIPT="${GIT_TOPLEVEL_DIR}/ci/asciidoc_validation/validate.sh"
|
||||
test -f "${VALIDATE_SCRIPT}"
|
||||
test -x "${VALIDATE_SCRIPT}"
|
||||
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)"
|
||||
|
||||
run_test() {
|
||||
# Run validation script on $1.
|
||||
# Ensure the output contains $2...$N.
|
||||
tmp="$(mktemp -d)"
|
||||
stderr_log="${tmp}/stderr_log"
|
||||
stdout_log="${tmp}/stdout_log"
|
||||
if TOPLEVEL="${SCRIPT_DIR}/$1" "${VALIDATE_SCRIPT}" 2> "${stderr_log}" > "${stdout_log}"
|
||||
then
|
||||
# The validation succeeded. We expect nothing in the output.
|
||||
[ $# -eq 1 ] # no tests
|
||||
test -f "${stderr_log}"
|
||||
{ ! test -s "${stderr_log}"; }
|
||||
else
|
||||
# We expect at least on check of the stderr content.
|
||||
[ $# -gt 1 ]
|
||||
shift
|
||||
|
||||
for query in "$@"
|
||||
do
|
||||
test -n "${query}"
|
||||
grep -q -e "${query}" "${stderr_log}"
|
||||
done
|
||||
fi
|
||||
|
||||
# Regardless of success or failure, the stdout is expected to be empty.
|
||||
test -f "${stdout_log}"
|
||||
{ ! test -s "${stdout_log}"; }
|
||||
}
|
||||
|
||||
run_test "test_valid"
|
||||
|
||||
run_test "test_unused_adoc" \
|
||||
"ERROR: These adoc files are not included anywhere:" \
|
||||
"rules/S100/java/unused.adoc" \
|
||||
"rules/S100/unused.adoc" \
|
||||
"shared_content/unused.adoc"
|
||||
|
||||
run_test "test_bad_cross_ref" \
|
||||
"ERROR: Some rules try to include content from unallowed directories." \
|
||||
"S100 cross-references .*rules/S1000/bad.adoc" \
|
||||
"S1000 cross-references .*rules/S100/java/bad.adoc"
|
||||
|
||||
run_test "test_diff_source" \
|
||||
"ERROR: Diff highlighting is used incorrectly:" \
|
||||
"\[S100/cfamily] diff-type is missing in .*/rules/S100/cfamily/rule.adoc:3" \
|
||||
"\[S100/cfamily] diff-id is missing in .*/rules/S100/cfamily/rule.adoc:8" \
|
||||
"\[S100/cfamily] diff-type 'bad' is not valid in .*/rules/S100/cfamily/rule.adoc:13" \
|
||||
"\[S100/cfamily] diff-type is missing in .*/rules/S100/cfamily/local.adoc:3" \
|
||||
"\[S100/cfamily] diff-id is missing in .*/rules/S100/cfamily/local.adoc:8" \
|
||||
"\[S100/cfamily] diff-type 'local' is not valid in .*/rules/S100/cfamily/local.adoc:13" \
|
||||
"\[S100/cfamily] diff-type is missing in .*/shared_content/cfamily/shared.adoc:3" \
|
||||
"\[S100/cfamily] diff-id is missing in .*/shared_content/cfamily/shared.adoc:8" \
|
||||
"\[S100/cfamily] diff-type 'shared' is not valid in .*/shared_content/cfamily/shared.adoc:13" \
|
||||
"\[S100/java] diff-id=1 has zero noncompliant example: .*/rules/S100/java/rule.adoc:3" \
|
||||
"\[S100/java] diff-id=2 has zero compliant example: .*/rules/S100/java/rule.adoc:8" \
|
||||
"\[S100/java] diff-id=3 has zero noncompliant example: .*/shared_content/java/example.adoc:3" \
|
||||
"\[S100/java] diff-id=4 has too many noncompliant examples: .*/rules/S100/java/rule.adoc:15, .*/shared_content/java/example.adoc:13, .*/shared_content/java/example.adoc:8" \
|
||||
"\[S200/default] diff-id=1 has too many noncompliant examples: .*/rules/S200/rule.adoc:12, .*/rules/S200/rule.adoc:2, .*/rules/S200/rule.adoc:7" \
|
||||
"\[S200/default] diff-id=2 has too many noncompliant examples: .*/rules/S200/rule.adoc:17, .*/rules/S200/rule.adoc:22" \
|
||||
"\[S200/default] diff-id=2 has zero compliant example: .*/rules/S200/rule.adoc:17, .*/rules/S200/rule.adoc:22"
|
||||
|
||||
echo "All tests passed"
|
@ -1,3 +0,0 @@
|
||||
Bad include from S100
|
||||
|
||||
include::../../S1000/bad.adoc[]
|
@ -1 +0,0 @@
|
||||
java S100
|
@ -1,3 +0,0 @@
|
||||
This include is fine
|
||||
|
||||
include::./bad.adoc[]
|
@ -1 +0,0 @@
|
||||
File from S1000
|
@ -1 +0,0 @@
|
||||
include::../../../shared_content/java/shared.adoc[]
|
@ -1 +0,0 @@
|
||||
include::../../rules/S100/java/bad.adoc[]
|
@ -1,15 +0,0 @@
|
||||
|
||||
[source,cpp,diff-id=1]
|
||||
----
|
||||
Missing diff-type
|
||||
----
|
||||
|
||||
[source,cpp,diff-type=compliant]
|
||||
----
|
||||
Missing diff-id
|
||||
----
|
||||
|
||||
[source,c,diff-id=1,diff-type=local]
|
||||
----
|
||||
Bad diff-type
|
||||
----
|
@ -1,19 +0,0 @@
|
||||
|
||||
[source,cpp,diff-id=1]
|
||||
----
|
||||
Missing diff-type
|
||||
----
|
||||
|
||||
[source,cpp,diff-type=compliant]
|
||||
----
|
||||
Missing diff-id
|
||||
----
|
||||
|
||||
[source,c,diff-id=1,diff-type=bad]
|
||||
----
|
||||
Bad diff-type
|
||||
----
|
||||
|
||||
include::./local.adoc[]
|
||||
|
||||
include::../../../shared_content/cfamily/shared.adoc[]
|
@ -1,17 +0,0 @@
|
||||
|
||||
[source,java,diff-id=1,diff-type=compliant]
|
||||
----
|
||||
1. compliant but missing noncompliant
|
||||
----
|
||||
|
||||
[source,java,diff-id=2,diff-type=noncompliant]
|
||||
----
|
||||
2. noncompliant but missing compliant
|
||||
----
|
||||
|
||||
include::../../../shared_content/java/example.adoc[]
|
||||
|
||||
[source,java,diff-id=4,diff-type=noncompliant]
|
||||
----
|
||||
4. noncompliant B
|
||||
----
|
@ -1,24 +0,0 @@
|
||||
[source,diff-id=1,diff-type=compliant]
|
||||
----
|
||||
1. compliant
|
||||
----
|
||||
|
||||
[source,diff-id=1,diff-type=noncompliant]
|
||||
----
|
||||
1. noncompliant A
|
||||
----
|
||||
|
||||
[source,diff-id=1,diff-type=noncompliant]
|
||||
----
|
||||
1. noncompliant B
|
||||
----
|
||||
|
||||
[source,diff-id=2,diff-type=noncompliant]
|
||||
----
|
||||
2. noncompliant A
|
||||
----
|
||||
|
||||
[source,diff-id=2,diff-type=noncompliant]
|
||||
----
|
||||
2. noncompliant B
|
||||
----
|
@ -1,15 +0,0 @@
|
||||
|
||||
[source,cpp,diff-id=1]
|
||||
----
|
||||
Missing diff-type
|
||||
----
|
||||
|
||||
[source,cpp,diff-type=compliant]
|
||||
----
|
||||
Missing diff-id
|
||||
----
|
||||
|
||||
[source,c,diff-id=1,diff-type=shared]
|
||||
----
|
||||
Bad diff-type
|
||||
----
|
@ -1,15 +0,0 @@
|
||||
|
||||
[source,java,diff-id=3,diff-type=compliant]
|
||||
----
|
||||
3. compliant but missing noncompliant
|
||||
----
|
||||
|
||||
[source,java,diff-id=4,diff-type=compliant]
|
||||
----
|
||||
4. compliant but too many noncompliant
|
||||
----
|
||||
|
||||
[source,java,diff-id=4,diff-type=noncompliant]
|
||||
----
|
||||
4. noncompliant A
|
||||
----
|
@ -1,3 +0,0 @@
|
||||
I am a main file.
|
||||
|
||||
include::./used.adoc[]
|
@ -1 +0,0 @@
|
||||
I am included.
|
@ -1,3 +0,0 @@
|
||||
I am a main file.
|
||||
|
||||
include::../../../shared_content/java/included.adoc[]
|
@ -1 +0,0 @@
|
||||
I got included.
|
@ -1 +0,0 @@
|
||||
another
|
@ -1,32 +0,0 @@
|
||||
S100 cfamily
|
||||
|
||||
include::./another.adoc[]
|
||||
|
||||
include::../../../shared_content/anything.adoc[]
|
||||
|
||||
[source,cpp,diff-id=1,diff-type=noncompliant]
|
||||
----
|
||||
noncompliant
|
||||
----
|
||||
|
||||
[source,cpp,diff-id=1,diff-type=compliant]
|
||||
----
|
||||
compliant
|
||||
----
|
||||
|
||||
|
||||
[source,cpp,diff-id=2,diff-type=noncompliant]
|
||||
----
|
||||
noncompliant
|
||||
----
|
||||
|
||||
[source,cpp,diff-id=2,diff-type=compliant]
|
||||
----
|
||||
compliant A
|
||||
----
|
||||
|
||||
[source,cpp,diff-id=2,diff-type=compliant]
|
||||
----
|
||||
compliant B
|
||||
----
|
||||
|
@ -1,3 +0,0 @@
|
||||
generic description
|
||||
|
||||
include::../../shared_content/anything.adoc[]
|
@ -1,3 +0,0 @@
|
||||
S200 java
|
||||
|
||||
include::../../../shared_content/anything.adoc[]
|
@ -1 +0,0 @@
|
||||
anything
|
@ -1,24 +0,0 @@
|
||||
= AsciiDoc Dos & Don'ts
|
||||
|
||||
=== Include
|
||||
|
||||
Make sure that `include` statements are surrounded by blank lines.
|
||||
AsciiDoc will sometimes (depending on the version) trim whitespaces at the beginning and end of the included files. Any adjacent text will thus be stuck to the inlined content, which could lead to display issues like swallowed title tags.
|
||||
|
||||
==== Write
|
||||
|
||||
----
|
||||
== Title
|
||||
|
||||
include::how-to-fix-it/core.adoc[]
|
||||
|
||||
include::how-to-fix-it/symfony.adoc[]
|
||||
----
|
||||
|
||||
==== Avoid
|
||||
|
||||
----
|
||||
== Title
|
||||
include::how-to-fix-it/core.adoc[]
|
||||
include::how-to-fix-it/symfony.adoc[]
|
||||
----
|
@ -1,51 +0,0 @@
|
||||
= Benchmarks
|
||||
|
||||
When writing a rule that has to do with performance, you might need to showcase some benchmarks as proof of potential improvements. This should be included in a separate section called `Benchmarks`, under `Resources`.
|
||||
|
||||
The format of the benchmarks section is as follows:
|
||||
|
||||
* Benchmark table
|
||||
* Benchmarking code
|
||||
* Hardware configuration
|
||||
|
||||
== Benchmark table
|
||||
|
||||
- Do not use abbreviations for statistical terms, not everyone is familiar with them.
|
||||
- In the `Benchmarks` section, add a subsection called `Glossary` right after the benchmark table and add links to Wikipedia to explain statistical terms used in the header of each column:
|
||||
|
||||
==== Glossary
|
||||
|
||||
* https://en.wikipedia.org/wiki/Arithmetic_mean[Mean]
|
||||
* https://en.wikipedia.org/wiki/Standard_deviation[Standard Deviation]
|
||||
* https://en.wikipedia.org/wiki/Memory_management[Allocated]
|
||||
|
||||
Ideally, the columns should look like in the following example:
|
||||
|
||||
|===
|
||||
| <What is being measured> | Mean | Standard Deviation | Allocated
|
||||
| <This> | 5.042 ms | 0.1049 ms | 125 KB
|
||||
| <That> | 2.691 ms | 0.0334 ms | 85.94 KB
|
||||
|===
|
||||
|
||||
== Benchmarking code
|
||||
|
||||
The code that was used to generate the benchmarks should be included to provide transparency and allow others to reproduce and verify the results. Preferably, the code snippet should include the sample size, the number of iterations, and the framework/library used to run the benchmarks.
|
||||
|
||||
If the code is not showcasing how the results were generated, consider prefixing it with an explanation that links to the framework/library used, for example:
|
||||
|
||||
----
|
||||
The results were generated by running the following snippet with https://github.com/dotnet/BenchmarkDotNet[BenchmarkDotNet].
|
||||
----
|
||||
|
||||
== Hardware configuration
|
||||
|
||||
The hardware configuration used to run the benchmarks should be included, for example:
|
||||
|
||||
[source]
|
||||
----
|
||||
BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.2846/22H2/2022Update)
|
||||
11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores
|
||||
[Host] : .NET Framework 4.8 (4.8.4614.0), X64 RyuJIT VectorSize=256
|
||||
.NET 7.0 : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
|
||||
.NET Framework 4.6.2 : .NET Framework 4.8 (4.8.4614.0), X64 RyuJIT VectorSize=256
|
||||
----
|
@ -1,21 +0,0 @@
|
||||
= Deprecation
|
||||
|
||||
== In the RSPEC
|
||||
|
||||
The rule status (`/status` in the metadata) should be set to `deprecated`, and its tags should be removed. If the rule is in `SonarWay`, it should be removed from this profile.
|
||||
|
||||
Optionally, `/extra/replacementRules` can list the rules that replace this rule.
|
||||
|
||||
The `superseded` status from Jira has been superseded by the `deprecated` status during transition to RSPEC V2.
|
||||
|
||||
== When running ruleAPI
|
||||
|
||||
We detect if replacement rules are specified and if they have been implemented or not.
|
||||
|
||||
* If they don't exist, the rule is exported as `deprecated`, and a text is added to the description
|
||||
* If they are specified, but have not been implemented, the rule is not considered as deprecated (its status is set to `ready` during the export).
|
||||
|
||||
* If they are specified and implemented, the status remains `deprecated`, and a text mentioning the replacement rules is added to the description.
|
||||
|
||||
In all cases, the `/extra/replacementRules` is removed from the metadata available to the analyzer.
|
||||
|
@ -1,378 +0,0 @@
|
||||
= Rule Description
|
||||
|
||||
:toc:
|
||||
|
||||
This document describes how `+rule.adoc+` and its dependencies should be structured.
|
||||
|
||||
See also the <<styling_guide.adoc#,Styling Guidelines>>, <<tone_guide.adoc#,Tone Guide>> and https://docs.sonarqube.org/latest/extension-guide/adding-coding-rules/#coding-rule-guidelines[Coding rule guidelines].
|
||||
|
||||
For technical support, see also
|
||||
|
||||
* Rule <<../rules/S6620/java/rule.adoc#,S6620>>, which showcases asciidoc features that are available when writing a rule description.
|
||||
* Rule <<../rules/S6778/java/rule.adoc#,S6778>>, which showcases multi-framework rules.
|
||||
|
||||
== Sections
|
||||
|
||||
There should be no first level titles (`+= Title+`) in your adoc.
|
||||
|
||||
The allowed second level titles and lower are described in their respective sections below, for each type of rule description.
|
||||
|
||||
== Types of rule description
|
||||
|
||||
There are currently 2 types of rule descriptions, each having a specific structure.
|
||||
|
||||
=== 1. Hotspot
|
||||
|
||||
The possible sections for this format are the following:
|
||||
|
||||
. Description (no title)
|
||||
. Ask Yourself Whether
|
||||
. Recommended Secure Coding Practices
|
||||
. Sensitive Code Example
|
||||
. Compliant Solution
|
||||
. Exceptions
|
||||
. See
|
||||
. See Also
|
||||
|
||||
Third-level and fourth-level titles (`+=== Title t3+` and `+==== Title t4+`) are not checked for this type of rule.
|
||||
|
||||
=== 2. Learn as You Code Rule Format
|
||||
|
||||
This format is defined as follows:
|
||||
|
||||
* Short description (no title)
|
||||
|
||||
// This needs to be kept in sync with the [maps in the validation script](https://github.com/SonarSource/rspec/blob/master/rspec-tools/rspec_tools/validation/description.py#L32-L39).
|
||||
* Why is this an issue? (mandatory)
|
||||
** What is the potential impact?
|
||||
** Exceptions
|
||||
** (any other titles)
|
||||
* How to fix it
|
||||
** Code examples
|
||||
*** Noncompliant code example
|
||||
*** Compliant solution
|
||||
** How does this work?
|
||||
** Pitfalls
|
||||
** Going the extra mile
|
||||
** (any other titles)
|
||||
* How to fix it in {Framework Display Name}
|
||||
** Code examples
|
||||
*** Noncompliant code example
|
||||
*** Compliant solution
|
||||
** How does this work?
|
||||
** Pitfalls
|
||||
** Going the extra mile
|
||||
** (any other titles)
|
||||
* Resources
|
||||
** Documentation
|
||||
** Articles & blog posts
|
||||
** Conference presentations
|
||||
** Standards
|
||||
** External coding guidelines
|
||||
** Benchmarks
|
||||
** Related rules
|
||||
|
||||
Where the `How to fix it in {Framework Display Name}` section can be repeated multiple times when the rule description is defined per-framework.
|
||||
|
||||
The sections and subsections for this format are defined as follows:
|
||||
|
||||
* Short description (no title) [Optional]
|
||||
+
|
||||
A short introduction/summary of the topic, only a few sentences. +
|
||||
Goal: The title (or message) of a rule might not always be clear due to its shortness, and this should make it clear to our users what the issue is about. Experienced developers will immediately understand what it is about, and developers not yet familiar with the issue at hand can dig deeper into the `Why is this an issue?` section.
|
||||
+
|
||||
* *Why is this an issue?* (level 2 title) *[Mandatory]*
|
||||
+
|
||||
Start at the basics and go into more depth to explain the concepts behind this type of issue. This is most likely the place where a lot of the content will be. +
|
||||
This is the place to tell the “story” of the rule, including the impact of leaving it unfixed. We should include code samples wherever needed to make it easier to understand
|
||||
what is going on. This can be in the form of noncompliant and compliant code in a single code box (noncompliant lines should always be highlighted with the corresponding comment
|
||||
“// Noncompliant” optionally followed by some explanation) if that is clearer. This first tab could use a freeform 'story-telling' style explaining what the rule is
|
||||
detecting and why. Feel free to use the “What is the potential impact?” title if it makes sense, or any other titles you find useful. +
|
||||
Goal: Understand the concepts behind an issue and why it matters.
|
||||
+
|
||||
** *What is the potential impact?* (level 3 title) [Optional]
|
||||
+
|
||||
This subsection aims to address questions such as the following: +
|
||||
What is the risk for me? What is the risk for my organization? What is the risk for the users? What can go wrong? +
|
||||
This section aims to talk about the impacts on the software, depending on the code qualities this issue is linked to: security, reliability, or maintainability. **(see <<metadata.adoc#code-field,Impacts>>.)**
|
||||
+
|
||||
Consequently, this section can also talk about the impacts on the ecosystem of this software. For example, impacts on the organization, on the users, and impact in terms of regulations.
|
||||
https://github.com/SonarSource/rspec/blob/a51217c6d91abfa5e1d77d0ae0843e3903adf2d0/rules/S3649/impact.adoc[_Example._] +
|
||||
Goal: Our users should understand the impact of this issue on their project.
|
||||
+
|
||||
* *How to fix it* or *How to fix it in `{Framework Display Name}`* (level 2 title) [Optional; the title depends on whether the description is generic or framework-specifc. See examples below.]
|
||||
+
|
||||
This section consists of one or multiple fixes for this type of issue (`Noncompliant code example` vs. `Compliant solution`). There can be multiple fixes for different libraries and/or frameworks.
|
||||
If the fix for the rule is trivial (quickfix is available, it is easily inferred from the title and/or message), this section should be omitted and the fix could be mentioned in the previous section.
|
||||
This tab could also use a freeform 'story-telling' style if that makes it clearer for the user. Feel free to use any of the titles below, or any other titles you find useful. +
|
||||
Goal: Get an idea of how this issue can be fixed for my project/framework, why this works, what to look out for, and also how to continue improving on this topic.
|
||||
+
|
||||
** *How does this work?* (level 3 title) [Optional]
|
||||
+
|
||||
Explain why this fixes the problem.
|
||||
+
|
||||
** *Pitfalls* (level 3 title) [Optional]
|
||||
+
|
||||
One or multiple pitfalls to take into account when working on fixing this issue.
|
||||
https://github.com/SonarSource/rspec/blob/a51217c6d91abfa5e1d77d0ae0843e3903adf2d0/rules/S6096/common/pitfalls/partial-path-traversal.adoc[_Example._]
|
||||
+
|
||||
** *Going the extra mile* (level 3 title) [Optional]
|
||||
+
|
||||
Even though the issue might be fixed, most of the time there can be way/s to further improve on this issue or to harden your project.
|
||||
The subsection should be concise.
|
||||
https://github.com/SonarSource/rspec/blob/a51217c6d91abfa5e1d77d0ae0843e3903adf2d0/rules/S5131/common/extra-mile/csp.adoc[_Example._]
|
||||
+
|
||||
* *Resources* (level 2 title) [Optional]
|
||||
+
|
||||
Include resources if our users want to dig even deeper, that can be presented in the different categories.
|
||||
https://github.com/SonarSource/rspec/tree/a51217c6d91abfa5e1d77d0ae0843e3903adf2d0/rules/S5131/common/resources[_Example._] +
|
||||
Goal: Allow the user to dig deeper by providing a curated list of resources.
|
||||
+
|
||||
** *Documentation* (level 3 title) [Optional]
|
||||
** *Articles & blog posts* (level 3 title) [Optional]
|
||||
** *Conference presentations* (level 3 title) [Optional]
|
||||
** *Standards* (level 3 title) [Optional]
|
||||
** *External coding guidelines* (level 3 title) [Optional]
|
||||
** *Benchmarks* (level 3 title) [Optional]
|
||||
** *Related rules* (level 3 title) [Optional]
|
||||
+
|
||||
This section lists Sonar rules related to the current one. The rule ID(s) should be followed by the rule title(s) or a sentence explaining the relation between the rules, e.g.: "_S2275 and S3457 specialize in detecting type mismatches with format strings._".
|
||||
|
||||
+
|
||||
xref:link_formatting.adoc[Standard for links is defined in this document.]
|
||||
|
||||
Content of the section "_How to fix it_ / _How to fix it in {Framework Display Name}_" can either be generic or framework specific.
|
||||
|
||||
When the content is generic, the "_How to fix it_" title must be used, and the section should only appear once. Example:
|
||||
....
|
||||
== Why is this an issue?
|
||||
Explanation of why this is bad.
|
||||
|
||||
== How to fix it
|
||||
|
||||
=== Code examples
|
||||
|
||||
==== Noncompliant code example
|
||||
[source,js,diff-id=1,diff-type=noncompliant]
|
||||
----
|
||||
var myExample;
|
||||
----
|
||||
|
||||
==== Compliant solution
|
||||
[source,js,diff-id=1,diff-type=compliant]
|
||||
----
|
||||
var myExample = 0;
|
||||
----
|
||||
|
||||
=== How does this work?
|
||||
We added something.
|
||||
|
||||
== Resources
|
||||
=== Documentation
|
||||
http-address-of-documentation[My doc name]
|
||||
|
||||
....
|
||||
Note that you can see two special attributes (`diff-id` and `diff-type`) used in the code examples above, these attributes are explained in the <<Diff view,Diff view>>
|
||||
section below.
|
||||
|
||||
When the content is framework-specific, one or more "_How to fix it in `{Framework Display Name}`_" sections (with their respective subsections) must be present.
|
||||
Each repetition will represent the specific _How to fix it_ section of a given framework.
|
||||
For example:
|
||||
....
|
||||
== How to fix it in Spring
|
||||
|
||||
=== Code examples
|
||||
... Some generic text and code examples for Spring...
|
||||
|
||||
=== How does this work?
|
||||
... Explanation about how the exploit works in Spring...
|
||||
|
||||
=== Pitfalls
|
||||
... Generic and Spring-specific pitfalls to avoid when fixing the issue...
|
||||
|
||||
== How to fix it in JSP
|
||||
|
||||
=== Code examples
|
||||
... Some generic text and code examples for JSP...
|
||||
|
||||
=== How does this work?
|
||||
... Explanation about how the exploit works in JSP...
|
||||
|
||||
=== Pitfalls
|
||||
... Generic and JSP-specific pitfalls to avoid when fixing the issue...
|
||||
....
|
||||
|
||||
Ideally, by convention and for maintainability, each framework _How to fix it_ section will be defined in separate files.
|
||||
Ex:
|
||||
....
|
||||
== Why is this an issue?
|
||||
... Explanation ...
|
||||
|
||||
# How to fix it sections
|
||||
|
||||
include::./how-to-fix-it/framework-1.adoc[]
|
||||
|
||||
include::./how-to-fix-it/framework-2.adoc[]
|
||||
|
||||
== Resources
|
||||
=== Documentation
|
||||
http-address-of-documentation[My doc name]
|
||||
....
|
||||
|
||||
Note that each framework-specific _How to fix it_ subsection must start with an H2 title following the given format:
|
||||
`== How to fix it in [an|a|the]? {Framework name}`.
|
||||
This is important, as this format will be expected by the analyzers when loading the rule content to recognize the different subsections.
|
||||
Furthermore, the display name of the framework has to match an allowed framework
|
||||
display name, as defined in <<header_names/allowed_framework_names.adoc#,this allowed framework names file>>.
|
||||
|
||||
==== General guidance
|
||||
|
||||
Most sections and subsections of the Learn as You Code rule format are optional, only the `Why is this an issue?` main section is mandatory.
|
||||
The goal is to provide the right level of guidance so that users get the right information from the rules at the right time.
|
||||
Do not feel obliged to use every section or sub-section if omitting them would lead to a better user experience.
|
||||
|
||||
==== Guidelines if you aren’t sure where something belongs in a rule
|
||||
* If you need the information to fix the issue, but only the first time, it probably belongs in ‘Why is this an issue?’
|
||||
* If you need the information to fix the issue every time, it probably belongs in ‘How to fix it?’
|
||||
* If you don’t need the information to fix the issue, but it will help users grow their knowledge, it probably belongs in ‘Resources’ (this maps to the 'More Info' tab in the products)
|
||||
|
||||
By being careful about what goes where, we help to ensure that users get exactly what they need, when they need it.
|
||||
|
||||
==== Guidelines on content focus
|
||||
|
||||
We want to help users to create Clean Code. Rule content should focus on the manner in which the code is not clean, why this is an issue, and how to remedy this.
|
||||
Rules should talk about the potential impact on software quality in the 'What is the potential impact?' sub-section.
|
||||
|
||||
For example, if you are talking about a locking issue, it makes sense to focus on the logical issues that could lead to a deadlock and how to fix that in the main rule content.
|
||||
The implications of a deadlock on the application reliability would then go into the 'What is the potential impact?' sub-section.
|
||||
|
||||
== Code Examples
|
||||
|
||||
Whenever possible, prefix your code blocks with `[source,language]`, in order to get syntax coloring.
|
||||
|
||||
....
|
||||
[source,cpp]
|
||||
----
|
||||
int main(int argc, const char** argv) {
|
||||
return 0;
|
||||
}
|
||||
----
|
||||
....
|
||||
|
||||
That is mandatory for the Noncompliant and Compliant code example sections, just recommended - at the moment - for other sections.
|
||||
|
||||
The language names accepted are usually the name we already use for the language folders in RSPEC. Exceptions are:
|
||||
|
||||
cfamily:: use `cpp`, `c`, or `objectivec`
|
||||
|
||||
plsql:: use `sql`
|
||||
|
||||
tsql:: use `sql`
|
||||
|
||||
In case no language is appropriate for a code block (for example shared examples between multiple languages), you can use `text` as the language.
|
||||
|
||||
=== Comments within code blocks
|
||||
|
||||
Colon (`:`) should be used as separator between `Noncompliant`/`Compliant` comments and the text explanation that follows, if any.
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
int X = 2; // Noncompliant: variable should be in lowercase
|
||||
----
|
||||
|
||||
|
||||
When referencing a name within a comment in a code example, use double quotes to make it clear it refers to an existing element in the code.
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
int i = 0;
|
||||
cout << noexcept(++i); // Noncompliant: "i" is not incremented
|
||||
----
|
||||
|
||||
=== Diff view
|
||||
|
||||
Additionally, you can also use two attributes to let the products know your code examples should be highlighted with a diff view when possible
|
||||
(showing the changes in the code examples as red/green).
|
||||
These attributes are optional and if a product does not yet support the diff view feature, these attributes will simply be ignored.
|
||||
|
||||
These attributes are `diff-id=X` and `diff-type=[noncompliant|compliant]`. The `diff-id` attributes describe which code examples should
|
||||
be compared together, and the `diff-type` attribute explain how it should be displayed `Noncompliant` (red) vs. `Compliant` (green).
|
||||
A single and unique diff-id should be used only once for each type of code example as shown in the description of a rule.
|
||||
....
|
||||
==== Noncompliant code example
|
||||
[source,js,diff-id=1,diff-type=noncompliant]
|
||||
----
|
||||
var myExample;
|
||||
----
|
||||
|
||||
==== Compliant solution
|
||||
[source,js,diff-id=1,diff-type=compliant]
|
||||
----
|
||||
var myExample = 0;
|
||||
----
|
||||
....
|
||||
|
||||
|
||||
== Parameters
|
||||
|
||||
Parameters should be listed in a subsection as follow:
|
||||
|
||||
....
|
||||
=== Parameters
|
||||
|
||||
.name
|
||||
****
|
||||
_TYPE_
|
||||
|
||||
----
|
||||
default value
|
||||
----
|
||||
|
||||
Description of what the parameter does.
|
||||
****
|
||||
|
||||
.name2
|
||||
****
|
||||
----
|
||||
another default value
|
||||
----
|
||||
Description of what this second parameter does.
|
||||
****
|
||||
|
||||
.name3
|
||||
****
|
||||
_TYPE_
|
||||
|
||||
Description of what this third parameter does.
|
||||
****
|
||||
|
||||
.name4
|
||||
****
|
||||
Description of what this fourth parameter does.
|
||||
****
|
||||
|
||||
....
|
||||
|
||||
The parameter name and the description are mandatory. The type and default value are not.
|
||||
|
||||
The parameter name with a `.` before will be the title of the block below marked by `****`.
|
||||
|
||||
We always use `----` around the default parameter to avoid having a special character confuse AsciiDoctor and to create a visual consistency for all parameters.
|
||||
|
||||
== Comment a rule
|
||||
|
||||
Comments and links that were created on Jira have been gathered in a `comments-and-links.adoc` file for each concerned rule. +
|
||||
You can add a comment anywhere in a rule by adding the following lines in the `*.adoc` file:
|
||||
[source]
|
||||
----
|
||||
\ifdef::env-github,rspecator-view[]
|
||||
John Doe (9 Jun 2021, 15:49): my comment on the rule
|
||||
\endif::env-github,rspecator-view[]
|
||||
----
|
||||
This way, your comment will only be visible in GitHub preview and on the Search Page (and will not be visible for the user).
|
||||
|
||||
== Share content between rules
|
||||
|
||||
You can share content between rules by using the `shared_content` folder at the root of the repository.
|
||||
|
||||
Any included content for a rule can only come from the folder of the rule being described, `shared_content`, or any of their subfolders.
|
@ -1,165 +0,0 @@
|
||||
// Ansible
|
||||
// C#
|
||||
* ASP.NET
|
||||
* ASP.NET Core
|
||||
* ASP.NET MVC 4.x
|
||||
* Razor
|
||||
* .NET
|
||||
* Entity Framework Core
|
||||
* Dapper
|
||||
* BouncyCastle
|
||||
* Jwt.Net
|
||||
* Blazor
|
||||
// C-Family
|
||||
* Botan
|
||||
* CryptoPP
|
||||
* OpenSSL
|
||||
* cURL
|
||||
* MSTest
|
||||
* NUnit
|
||||
* xUnit
|
||||
* Fluent Assertions
|
||||
* NFluent
|
||||
* NSubstitute
|
||||
* MSTest
|
||||
* NUnit
|
||||
* Xerces
|
||||
* libxml2
|
||||
// Java
|
||||
* Android
|
||||
* Android WebView
|
||||
* Apache Commons
|
||||
* Apache Commons
|
||||
* Apache Commons Email
|
||||
* Apache HttpClient
|
||||
* Auth0 JWT
|
||||
* Commons Compiler
|
||||
* Dom4j
|
||||
* FasterXML
|
||||
* Groovy
|
||||
* Gson
|
||||
* Hibernate
|
||||
* Java Cryptography Extension
|
||||
* Java EE
|
||||
* Java JWT
|
||||
* Java SE
|
||||
* Java JDBC API
|
||||
* Java I/O API
|
||||
* Jdom2
|
||||
* JSP
|
||||
* Legacy Mongo Java API
|
||||
* OkHttp
|
||||
* Realm
|
||||
* Apache HttpClient
|
||||
* Couchbase
|
||||
* SAX
|
||||
* Servlet
|
||||
* Spring
|
||||
* Spring Data MongoDB
|
||||
* Spring Data Cassandra
|
||||
* Spring Data Redis
|
||||
* Spring Data Neo4j
|
||||
* SQLCipher
|
||||
* Thymeleaf
|
||||
// JS
|
||||
* Jasmine
|
||||
* Jest
|
||||
* Flow.js
|
||||
* Node.js
|
||||
* Express.js
|
||||
* SSH2
|
||||
* Mocha
|
||||
* MongoDB
|
||||
* Mongoose
|
||||
* Sequelize
|
||||
* Knex
|
||||
* DOM API
|
||||
* jsonwebtoken
|
||||
* libxmljs
|
||||
* Formidable
|
||||
* Multer
|
||||
* Passport
|
||||
* Request
|
||||
* TypeScript
|
||||
* PropTypes
|
||||
* JSX
|
||||
* Electron
|
||||
// PHP
|
||||
* Core PHP
|
||||
* Guzzle
|
||||
* Laminas
|
||||
* Laravel
|
||||
* Symfony
|
||||
* WordPress
|
||||
* Mcrypt
|
||||
// Python
|
||||
* aiohttp
|
||||
* Amazon DynamoDB
|
||||
* Argon2-cffi
|
||||
* Bcrypt
|
||||
* Cryptodome
|
||||
* databases
|
||||
* Django
|
||||
* Django Templates
|
||||
* FastAPI
|
||||
* Flask
|
||||
* HTTPX
|
||||
* Jinja
|
||||
* lxml
|
||||
* MySQL Connector/Python
|
||||
* Numpy
|
||||
* Paramiko
|
||||
* pyca
|
||||
* PyCrypto
|
||||
* pyDes
|
||||
* PyJWT
|
||||
* pyOpenSSL
|
||||
* python-jose
|
||||
* python-jwt
|
||||
* python-ldap
|
||||
* Python SQLite
|
||||
* Python Standard Library
|
||||
* PyTorch
|
||||
* PyYAML
|
||||
* Requests
|
||||
* Scrypt
|
||||
* Scikit-Learn
|
||||
* SignXML
|
||||
* SQLAlchemy
|
||||
* ssl
|
||||
* TensorFlow
|
||||
// Docker
|
||||
* Wget
|
||||
// Cloudformation
|
||||
* API Gateway
|
||||
* OpenSearch
|
||||
* Identity and Access Management
|
||||
// Azure Resource Manager
|
||||
* Storage Accounts
|
||||
* Databases
|
||||
* ARM Templates
|
||||
* Bicep
|
||||
// Terraform
|
||||
* AWS API Gateway
|
||||
* AWS OpenSearch
|
||||
* Azure Databases
|
||||
* Azure Storage Accounts
|
||||
* GCP Load Balancers
|
||||
* AWS Identity and Access Management
|
||||
// CDK
|
||||
* AWS CDK
|
||||
// Swift
|
||||
* CommonCrypto
|
||||
* CryptoSwift
|
||||
* IDZSwiftCommonCrypto
|
||||
// Azure resource manager
|
||||
* JSON templates
|
||||
* Bicep
|
||||
// PL/SQL
|
||||
* DBMS_CRYPTO
|
||||
// Go
|
||||
* Go Standard Library
|
||||
// Kubernetes
|
||||
* Helm
|
||||
// Kotlin
|
||||
Jetpack Compose
|
@ -1,6 +0,0 @@
|
||||
* Ask Yourself Whether
|
||||
* Sensitive Code Example
|
||||
* Recommended Secure Coding Practices
|
||||
* Compliant Solution
|
||||
* Exceptions
|
||||
* See
|
@ -1,83 +0,0 @@
|
||||
== Why is it important to define a standard for links?
|
||||
|
||||
We often want to provide links in the 'Resources' section of a rule that point to blog posts, documentation, and other rules. To provide a consistent experience across the Sonar solution, we want to define standard ways to write these links in our rules. This document aims to do that. This guidance does not apply to links that appear within continuous text elsewhere in a rule.
|
||||
|
||||
== How should you write links?
|
||||
|
||||
=== Link formatting:
|
||||
|
||||
* Links to documentation: <source> - <title> e.g. SonarQube Documentation - SonarScanner for Gradle
|
||||
* Links to framework documentation <source> - <member name and kind> e.g. Microsoft Learn - Object.ToString Method
|
||||
* Links to blog posts / news articles: <source> - <title> e.g. Google Security Blog - Moving towards a more secure web
|
||||
* Links to Stack Overflow answers (similar comments in a blog)e.g. Stack Overflow - Answer by Stephen Cleary for Best way to handle null task inside async method?
|
||||
* Links to another Sonar rule: <rulenumber> - <title> e.g. S2755 - XML parsers should not be vulnerable to XXE attacks.
|
||||
Note that rule-ids (`<rulenumber>`) are automatically hyperlinked in our products to point to the rule description in that product.
|
||||
_Do not add any hyperlink yourself._
|
||||
|
||||
The hyperlink to anything apart from other Sonar rules should be applied to just the document name, with the 'source' being left as plain text. The idea is that this makes it really easy for a user to understand the source before they click on anything.
|
||||
For Sonar rules, the whole entry (<rulenumber> - <title>) should be a hyperlink.
|
||||
|
||||
Here is how the above links should look like:
|
||||
|
||||
* SonarQube Documentation - https://docs.sonarqube.org/9.7/analyzing-source-code/scanners/sonarscanner-for-gradle/[SonarScanner for Gradle]
|
||||
* Microsoft Learn - https://learn.microsoft.com/en-us/dotnet/api/system.object.tostring[`Object.ToString` Method]
|
||||
* Google Security Blog - https://security.googleblog.com/2016/09/moving-towards-more-secure-web.html[Moving towards a more secure web]
|
||||
* Stack Overflow - Answer by Stephen Cleary for https://stackoverflow.com/a/27551261[Best way to handle null task inside async method?]
|
||||
* S2755 - XML parsers should not be vulnerable to XXE attacks +
|
||||
_Note, no link here, this is the intended behavior. In the products the "S2755" part will be automatically hyperlinked._
|
||||
|
||||
|
||||
=== Additional things to consider when adding a link to a rule:
|
||||
|
||||
* Is the article (particularly blog posts) likely to be around for at least 6 months?
|
||||
* Do you trust this source as an 'expert'?
|
||||
* Link to en_US versions where there is a choice
|
||||
|
||||
=== Short form names for sources we regularly use
|
||||
|
||||
When web pages have massively long names like "Java™ Platform, Standard Edition 8 API Specification", we will provide short form names that we will use instead. Feel free to add new ones below:
|
||||
|
||||
==== Short form names to use:
|
||||
|
||||
* Android Documentation - https://developer.android.com/docs
|
||||
* Apex Developers Guide - https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_keywords_sharing.htm
|
||||
* AWS Documentation - https://docs.aws.amazon.com/
|
||||
* AWS blog - https://aws.amazon.com/blogs
|
||||
* Azure Documentation - https://learn.microsoft.com/en-us/azure/?product=popular
|
||||
* CERT - https://wiki.sei.cmu.edu/confluence/display/seccode
|
||||
* Clippy Lints - https://rust-lang.github.io/rust-clippy/master/index.html
|
||||
* {cpp} reference - https://en.cppreference.com/w/
|
||||
* {cpp} Core Guidelines - https://github.com/isocpp/CppCoreGuidelines/blob/e49158a/CppCoreGuidelines.md
|
||||
* CVE - https://cve.mitre.org
|
||||
* CWE - https://cwe.mitre.org
|
||||
* Docker Documentation - https://docs.docker.com/
|
||||
* Google Cloud - https://cloud.google.com/docs
|
||||
* Java Documentation - https://docs.oracle.com/en/java/
|
||||
* Kotlin Documentation - https://kotlinlang.org/docs/home.html
|
||||
* Kubernetes Documentation - https://kubernetes.io/docs/home/
|
||||
* Microsoft Learn - https://learn.microsoft.com/en-us/
|
||||
* Microsoft Developer Blog - https://devblogs.microsoft.com/
|
||||
* MDN web docs - https://developer.mozilla.org/en-US/
|
||||
* Medium - https://medium.com/
|
||||
* MITRE - https://attack.mitre.org/
|
||||
* Mockito - https://site.mockito.org/javadoc/current/overview-summary.html
|
||||
* The Open Group - https://www.opengroup.org/
|
||||
* OWASP - https://owasp.org/
|
||||
* PEP - https://peps.python.org/
|
||||
* PHP Documentation - https://www.php.net/docs.php
|
||||
* PHP Tutorials - https://www.phptutorial.net/
|
||||
* PEP 8 - Style Guide for Python Code - https://peps.python.org/pep-0008/
|
||||
* Python Documentation - https://docs.python.org/
|
||||
* React Documentation - https://reactjs.org/
|
||||
* Rhino Security Labs - https://rhinosecuritylabs.com/
|
||||
* SAP Documentation - http://help.sap.com/abapdocu_702/en/abenabap.htm
|
||||
* SonarQube Documentation - https://docs.sonarqube.org/latest/
|
||||
* Sonar - https://www.sonarsource.com/
|
||||
* Sonar Blog - https://www.sonarsource.com/blog/
|
||||
* Stack Overflow - https://stackoverflow.com/
|
||||
* Symfony - https://symfony.com/doc/current/index.html
|
||||
* Test NG Documentation - https://testng.org/doc/documentation-main.html
|
||||
* W3Schools - https://www.w3schools.com/
|
||||
* WCAG - https://www.w3.org/WAI/standards-guidelines/wcag/
|
||||
* Wikipedia - https://en.wikipedia.org
|
||||
|
@ -1,45 +0,0 @@
|
||||
ifdef::env-github[]
|
||||
:tip-caption: :bulb:
|
||||
:note-caption: :information_source:
|
||||
endif::[]
|
||||
= Rule Metadata
|
||||
|
||||
This document describes how `+metadata.json+` should be structured.
|
||||
|
||||
== `title`, `type`, `tags`, `remediation`, and `defaultSeverity`
|
||||
|
||||
These fields are described in the https://docs.sonarqube.org/latest/extension-guide/adding-coding-rules/#coding-rule-guidelines[SonarQube documentation].
|
||||
|
||||
== `quickfix` field
|
||||
|
||||
Every active rule that is not a security hotspot must specify the availability of a quick fix for its issues.
|
||||
`metadata.json` must feature a `quickfix` field with one of the following values:
|
||||
|
||||
* `unknown`: the feasibility of producing a quick fix is not evaluated.
|
||||
* `infeasible`: it is not feasible to propose a quick fix for any of the issues, for whatever reason.
|
||||
* `targeted`: it is possible to implement quick fixes for this rule, but none are implemented right now.
|
||||
* `partial`: some of the issues produced by the rule propose a quick fix, but not all.
|
||||
* `covered`: all the issues produced by the rule propose a quick fix.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
A `covered` rule is still not guaranteed to provide a quick fix for a particular issue (for instance, if a fix location would be inside a macro expansion or in a different file from the issue location). The rule is said to have a `partial` quick fix only if there exists a family of issues that cannot have a quick fix.
|
||||
|
||||
For instance, on one hand, if a rule detects two functions that are dangerous to use, `A` and `B`, and `A` has an obvious replacement (and therefore a quick fix) while `B` does not, the field should be set to `partial`.
|
||||
|
||||
On the other hand, if a quick fix could be easily proposed for both `A` and `B`, but the fix location might be inside a macro expansion, or in a different file from the issue location (and hence not feasible given the current SonarLint capabilities), this should not prevent the rule from being tagged as `covered`.
|
||||
====
|
||||
|
||||
[TIP]
|
||||
====
|
||||
You can update the quickfix field using this GitHub Workflow: https://github.com/SonarSource/rspec/actions/workflows/update_quickfix_status.yml
|
||||
====
|
||||
|
||||
|
||||
== `code` field
|
||||
|
||||
The code field is an object that contains information related to the clean code taxonomy. It is an object with two required properties:
|
||||
|
||||
* `impacts`: A nested object that is treated as a mapping from a software quality to a level (`"INFO"`, `"LOW"`, `"MEDIUM"`, `"HIGH"` or `"BLOCKER"`). Note that at least one software quality has to be specified. The current list of allowed software qualities is `"MAINTAINABILITY"`, `"RELIABILITY"` and `"SECURITY"`.
|
||||
|
||||
* `attribute`: A single clean code attribute that the rule aims to achieve. This has to be one of the following values: `"FORMATTED"`, `"CONVENTIONAL"`, `"IDENTIFIABLE"`, `"CLEAR"`, `"LOGICAL"`, `"COMPLETE"`, `"EFFICIENT"`, `"FOCUSED"`, `"DISTINCT"`, `"MODULAR"`, `"TESTED"`, `"LAWFUL"`, `"TRUSTWORTHY"`, `"RESPECTFUL"`.
|
@ -1,125 +0,0 @@
|
||||
= Styling Guide
|
||||
|
||||
This document provides styling guidelines for `+rule.adoc+` and its dependencies.
|
||||
|
||||
See also the <<description.adoc#,Description Guidelines>> for information about the rule structure and the <<tone_guide.adoc#,Tone Guide>> for guidance on the tone of voice to use.
|
||||
|
||||
|
||||
The RSPEC styling guide is loosely based on the Associated Press Style and geared to rule descriptions.
|
||||
The official Associated Press Style should be used as a fall-back for topics not defined here.
|
||||
The guide might be extended in the future when unanimities emerge.
|
||||
|
||||
|
||||
Following it should be considered a goal to work towards to get more homogenous texts.
|
||||
This will be a long process consisting of many baby steps, such as adjusting the style when rule descriptions are rewritten for the Progressive Education Framework.
|
||||
It is acceptable not to have 100% consistency across all texts. The higher the consistency, the better, but differences are expected.
|
||||
|
||||
== Language
|
||||
|
||||
Use _American English_ and its notation.
|
||||
|
||||
== Acronyms
|
||||
|
||||
Do not use acronyms without defining them first unless they are considered well known by the target audience.
|
||||
For example, the acronym _JDK_ can be considered common knowledge for a Java developer and does not have to be defined.
|
||||
The definition of what is and is not a well-known acronym is somewhat subjective.
|
||||
It is up to the reviewers of RSPEC pull requests to challenge the use of acronyms that might not be well known.
|
||||
|
||||
If possible and sensible, spell out the acronym on first use and use generic terms on subsequent mentions.
|
||||
For example, refer to _Cross-Site Scripting_ as _the vulnerability_.
|
||||
|
||||
Write:: Cross-Site Scripting allows to inject JavaScript code in the context of a website. The vulnerability can be abused to hijack sessions.
|
||||
Avoid:: XSS allows to inject JS code in the context of a website. XSS can be abused to hijack sessions.
|
||||
|
||||
== Contractions
|
||||
|
||||
Contractions are considered informal writing and therefore should not be used.
|
||||
|
||||
Write:: It is the right way!
|
||||
Avoid:: It's not the right way!
|
||||
|
||||
== Numbers
|
||||
|
||||
In general, spell out numbers one through nine. Use digits for numbers 10 and higher.
|
||||
Be aware that there are many exceptions to this rule. For instance, use digits for units or percentages.
|
||||
|
||||
Write:: 5°C is 9% warmer than yesterday. The condition checks if the value is greater than eight and smaller than 100.
|
||||
Avoid:: The condition checks if the value is greater than 8 and smaller than one hundred.
|
||||
|
||||
|
||||
Commas should be used to group three digits of numbers larger than 999.
|
||||
|
||||
Write:: My program creates 1,000,000 forks.
|
||||
Avoid:: My program creates 1000000 forks.
|
||||
|
||||
== Punctuation
|
||||
|
||||
=== Colon
|
||||
|
||||
Do not capitalize the first word after a colon unless it is the start of a sentence or a proper noun.
|
||||
|
||||
Write:: There is only one thing we can do: rewrite.
|
||||
Avoid:: There is only one thing we can do: Rewrite.
|
||||
|
||||
=== Comma
|
||||
|
||||
To avoid ambiguity, add the Oxford comma after the penultimate term in a series of three or more terms.
|
||||
|
||||
Write:: My code is slick and works. My code is not slow, unreliable, and full of bugs.
|
||||
Avoid:: My code is slick, and works. My code is not slow, unreliable and full of bugs.
|
||||
|
||||
=== Parentheses
|
||||
|
||||
Parentheses should be avoided. If the information is relevant, it is preferred to incorporate it directly in a sentence.
|
||||
|
||||
Write:: This is a test that forms an example.
|
||||
Avoid:: This is a test (example).
|
||||
|
||||
== Lists
|
||||
|
||||
Do not capitalize the first word of list entries unless it is the start of a sentence or a proper noun.
|
||||
|
||||
Write::
|
||||
Check the values:
|
||||
* size
|
||||
* length
|
||||
|
||||
Avoid::
|
||||
Check the values:
|
||||
* Size
|
||||
* Length
|
||||
|
||||
Do not add punctuation for enumerations.
|
||||
|
||||
Write::
|
||||
Check the values:
|
||||
* size
|
||||
* length
|
||||
|
||||
Avoid::
|
||||
Check the values:
|
||||
* size,
|
||||
* length.
|
||||
|
||||
== Literals
|
||||
|
||||
Inline literals (backticks) should be used to highlight short values.
|
||||
Use it when referencing variable names, file names, tokens, and all kinds of specific strings of text that should be visually extracted from the surrounding default text.
|
||||
|
||||
Write:: Compiling source file `src/generic_file.py` breaks an `assert` call in pytest framework.
|
||||
Avoid:: Compiling source file "src/generic_file.py" breaks an `assert` call in `pytest` framework.
|
||||
|
||||
== Referencing elements from the code
|
||||
|
||||
When referencing elements from the code within a normal sentence, use the `backticks` (```) to format it. This includes variable names, function names, class names, and so on.
|
||||
|
||||
When referencing the same elements within a comment in a code block, surrpond it with double quotes.
|
||||
[source,cpp]
|
||||
----
|
||||
int i = 0;
|
||||
// Write
|
||||
cout << noexcept(++i); // Noncompliant, "i" is not incremented -> Double quotes
|
||||
// Avoid
|
||||
cout << noexcept(++i); // Noncompliant, i is not incremented -> No quotes
|
||||
cout << noexcept(++i); // Noncompliant, `i` is not incremented -> Backticks
|
||||
----
|
@ -1,66 +0,0 @@
|
||||
= Testing changes
|
||||
|
||||
This document explains how to test changes to the frontend, CI, and rspec-tools.
|
||||
|
||||
Refer to the <<../README.adoc#AddModifyRule,main documentation>> if you want to modify rules.
|
||||
|
||||
== Testing the frontend
|
||||
|
||||
If your changes are small and noncontroversial, you can directly create a branch in this repository.
|
||||
|
||||
However, when you need to show reviewers how the website will look with your changes,
|
||||
you can use a fork with your own GitHub Pages.
|
||||
See <<forking>> for more details.
|
||||
|
||||
To test your changes locally, you can start a local HTTP server, such as ``npm start``.
|
||||
Refer to the frontend documentation for more details.
|
||||
|
||||
== Testing the rspec-tools
|
||||
|
||||
Modifications to the rspec-tools do not require any special process.
|
||||
You can therefore use a regular PR from a branch in this repository.
|
||||
|
||||
== Testing GitHub Actions
|
||||
|
||||
There are two strategies to test GitHub Actions: either use your own branch in this repository, or create a fork.
|
||||
|
||||
Choose the solution your need based on the impact your changes can have.
|
||||
For example, if the changes may spam/modify other Pull Requests, it is wiser to use your own fork.
|
||||
|
||||
Note that GitHub will pick the workflow definitions from your branch whether it is in a fork or not.
|
||||
It will also run new workflow scripts automatically (i.e. there is no configuration to change on GitHub).
|
||||
When working on a new workflow or updating existing ones, since it may confuse other people to see unexpected results,
|
||||
it is preferable to use your own fork while iterating on your work.
|
||||
|
||||
See <<forking>> for more details.
|
||||
|
||||
== Testing Cirrus CI
|
||||
|
||||
To test modification to the Cirrus CI script, you need to create your own branch in this repository (not a fork).
|
||||
Cirrus CI will automatically pick up the version of the scripts on that branch.
|
||||
|
||||
[[forking]]
|
||||
== Forking
|
||||
|
||||
Forking this repository is fairly trivial.
|
||||
|
||||
. Click "Fork" on the top right and select your own account.
|
||||
|
||||
. If needed, enable GitHub Actions:
|
||||
.. in your fork, go to the "Actions" page,
|
||||
.. click on the green button to enable workflows,
|
||||
.. and, if needed, additionally enable the "Update rule coverage" workflow,
|
||||
which is disabled by default because it executes on a schedule.
|
||||
|
||||
. If needed, enable GitHub Pages:
|
||||
.. enable the GitHub Actions so that the ``gh-pages`` branch is properly populated,
|
||||
.. go to "Settings" > "Pages" and under "Source"
|
||||
*** set "Branch" to "gh-pages",
|
||||
*** select "/ (root)" as the source folder,
|
||||
*** and click "save".
|
||||
|
||||
If you need to rely on GitHub Actions, you can work on your fork's ``master`` branch to ensure that
|
||||
the "Build and Deploy" workflow gets triggered out of the box.
|
||||
|
||||
Once you are done with your feature and your PR was merged, you can delete your fork.
|
||||
If you prefer to keep the fork alive, don't forget to merge the upstream changes before working on your next feature.
|
@ -1,37 +0,0 @@
|
||||
= Tone Guide
|
||||
|
||||
When writing a rule, we must consider who we are talking to, what they are trying to achieve, and what their mindset is likely to be.
|
||||
|
||||
== Who are we talking to?
|
||||
|
||||
We will be talking to a range of people; experienced engineers fixing something for the 10th time, people very new to the language, working on code they don't know well, and everyone in between. It is also important to realize that many of our users are not native English speakers.
|
||||
|
||||
What might they be doing, and how do they feel?
|
||||
|
||||
* They want to merge a PR and have a failing quality gate
|
||||
** They may be tired, stressed, embarrassed, or under pressure to finish
|
||||
*** Now is the time for a calm, well-informed mentor or coach, not for criticism, fear, or excessive humor
|
||||
|
||||
* They just saw they have a potential security hole
|
||||
** They may be worrying about the implications for their job or getting a reprimand from a stern boss or security owner. They may be thinking this is yet another false positive and hovering over the ‘won’t fix’ button.
|
||||
*** We need to be calm and factual. There is no need to scare them unduly and a friendly tone will definitely be helpful.
|
||||
|
||||
* They got that green quality gate, are basking in their success, and considering what to do next
|
||||
** How can we help them turn that cheerful glow into motivation to learn more?
|
||||
|
||||
== Guidelines
|
||||
|
||||
*Tone:* We are your coding mentor. We are friendly and trustworthy but reasonably serious; we know what we are talking about, and we want to help you make the right change and grow in the process, at the right time for you.
|
||||
|
||||
* Aim for brevity and clarity in the 'Why is this an issue' and 'How to fix it' sections.
|
||||
** Aim for just enough to make it clear what the key points are and no more. You can always add interesting stuff to the 'more info' section for when they have some spare time and want to learn more.
|
||||
** Try to use simple English and avoid slang. Assume users have high school-level English, equivalent to CEFR levels B1/B2, at best. How easy can we make it to understand complex ideas?
|
||||
|
||||
* Write in the active voice rather than the passive.
|
||||
** 'The user logged into their account,' not 'The account was logged into by the user.'
|
||||
|
||||
* Try to avoid unnecessary jargon
|
||||
** If in doubt, explain it or link to a good explanation
|
||||
|
||||
* Try to be positive
|
||||
** We want them to understand the facts but not scare them unnecessarily. We are part of the solution, not the problem.
|
26
frontend/.gitignore
vendored
26
frontend/.gitignore
vendored
@ -1,26 +0,0 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
.eslintcache
|
||||
incomplete-rules-repo/
|
@ -1,50 +0,0 @@
|
||||
|
||||
= Search page for the rule repository
|
||||
|
||||
This is a single-page React application that indexes the rule repository and
|
||||
allows you to run flexible search through all of the rules and rule drafts,
|
||||
and render the rule specifications in HTML.
|
||||
The render is not guaranteed to match the rule rendering in the products,
|
||||
but it is a good proxy.
|
||||
|
||||
== Local deployment
|
||||
|
||||
Make sure you have NodeJs and `npm` available. Tested with NPM v10.2.3 and NodeJS v18.19.0.
|
||||
|
||||
Install dependencies
|
||||
|
||||
[source,shell]
|
||||
----
|
||||
npm install
|
||||
----
|
||||
|
||||
If you have some non-js dependencies missing, this might fail.
|
||||
Possibly missing non-js dependencies include OpenSSL, libuv, libssh2, KRB5.
|
||||
|
||||
Once you succeed in installing the dependencies you are ready to predeploy.
|
||||
|
||||
[source,shell]
|
||||
----
|
||||
npm run predeploy
|
||||
----
|
||||
|
||||
This command builds the database of the rule specifications.
|
||||
|
||||
NOTE: If the script fails to clone or fetch due to an SSL certificate failure
|
||||
and your network uses a custom CA certificate you might need to make sure it is installed
|
||||
in the accessible place.
|
||||
As a workaround you can https://github.com/nodegit/nodegit/issues/1742[disable the certificate check].
|
||||
|
||||
NOTE: In the predeploy step (specifically the `prepare-rules` part of it) the script fetches all the open PRs locally.
|
||||
You might want to set `GITHUB_TOKEN` to your personal GitHub token
|
||||
to avoid GitHub throttling your requests during the predeploy stage.
|
||||
|
||||
|
||||
Now you can run it locally:
|
||||
|
||||
[source,shell]
|
||||
----
|
||||
npm start
|
||||
----
|
||||
|
||||
This should open https://localhost:3000/rspec in your default browser with the rule search page.
|
20336
frontend/package-lock.json
generated
20336
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,72 +0,0 @@
|
||||
{
|
||||
"name": "rspec",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"homepage": "http://sonarsource.github.io/rspec",
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.12.4",
|
||||
"@material-ui/icons": "^4.11.3",
|
||||
"@material-ui/lab": "^4.0.0-alpha.61",
|
||||
"@octokit/rest": "^18.12.0",
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^11.2.7",
|
||||
"@testing-library/user-event": "^12.8.3",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@types/node": "^12.20.55",
|
||||
"@types/react": "^16.14.49",
|
||||
"@types/react-dom": "^16.9.20",
|
||||
"asciidoctor": "^2.2.6",
|
||||
"html-react-parser": "^1.4.14",
|
||||
"lunr": "^2.3.9",
|
||||
"node-html-parser": "^5.4.2",
|
||||
"nodegit": "^0.28.0-alpha.24",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-highlight": "^0.14.0",
|
||||
"react-router-dom": "^5.3.4",
|
||||
"react-scripts": "^5.0.1",
|
||||
"setimmediate": "^1.0.5",
|
||||
"string-strip-html": "^8.5.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.5",
|
||||
"verror": "^1.10.1",
|
||||
"web-vitals": "^0.2.4",
|
||||
"winston": "^3.11.0",
|
||||
"yargs": "^16.2.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"prepare-rules": "ts-node -P tsconfig-rules.json ./src/deployment/index.ts",
|
||||
"predeploy": "npm run prepare-rules && npm run build",
|
||||
"deploy": "gh-pages -d build"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/react-hooks": "^7.0.2",
|
||||
"@types/lunr": "^2.3.5",
|
||||
"@types/nodegit": "^0.27.10",
|
||||
"@types/react-highlight": "^0.12.6",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"tmp-promise": "^3.0.3"
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Before Width: | Height: | Size: 832 B |
@ -1,54 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html prefix="og: https://ogp.me/ns#" lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Rules Repository Search Page"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo128.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.png" or "favicon.png", "%PUBLIC_URL%/favicon.png" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>RSPEC</title>
|
||||
<!-- Twitter Card -->
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:image" content="%PUBLIC_URL%/favicon.png" />
|
||||
<meta name="twitter:title" content="RSPEC" />
|
||||
<meta name="twitter:description" content="Rules Repository Search Page" />
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:title" content="RSPEC" />
|
||||
<meta property="og:description" content="Rules Repository Search Page" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="%PUBLIC_URL%" />
|
||||
<meta property="og:image" content="%PUBLIC_URL%/favicon.png" />
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
Binary file not shown.
Before Width: | Height: | Size: 2.1 KiB |
Binary file not shown.
Before Width: | Height: | Size: 9.1 KiB |
@ -1,25 +0,0 @@
|
||||
{
|
||||
"short_name": "RSPEC",
|
||||
"name": "Rules Repository Search Page",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.png",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo128.png",
|
||||
"type": "image/png",
|
||||
"sizes": "128x128"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
@ -1,10 +0,0 @@
|
||||
sonar.projectKey=rspec-frontend
|
||||
sonar.projectName=rspec-frontend
|
||||
|
||||
sonar.sources=src
|
||||
sonar.exclusions=**/*.test.js,**/*.test.jsx,**/*.test.ts,**/*.test.tsx
|
||||
sonar.tests=src
|
||||
sonar.test.inclusions=**/*.test.js,**/*.test.jsx,**/*.test.ts,**/*.test.tsx
|
||||
sonar.javascript.coveragePlugin=lcov
|
||||
sonar.javascript.lcov.reportPaths=coverage/lcov.info
|
||||
sonar.cpd.exclusions=src/deployment/__tests__/resources/**
|
@ -1,9 +0,0 @@
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
root: {
|
||||
textAlign: 'left',
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
appBarSpacer: theme.mixins.toolbar,
|
||||
}));
|
@ -1,36 +0,0 @@
|
||||
import React from 'react';
|
||||
import CssBaseline from '@material-ui/core/CssBaseline';
|
||||
import { Box } from '@material-ui/core';
|
||||
import useStyles from './App.style';
|
||||
import {
|
||||
HashRouter as Router,
|
||||
Switch,
|
||||
Route
|
||||
} from "react-router-dom";
|
||||
import {RulePage} from "./RulePage";
|
||||
import {SearchPage} from "./SearchPage";
|
||||
import TopBar from "./TopBar";
|
||||
|
||||
|
||||
function App() {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<CssBaseline>
|
||||
<div className={classes.root}>
|
||||
<TopBar/>
|
||||
<Box>
|
||||
<Router basename="/rspec">
|
||||
<Switch>
|
||||
<Route path="/:ruleid/:language?" component={RulePage} />
|
||||
<Route>
|
||||
<SearchPage/>
|
||||
</Route>
|
||||
</Switch>
|
||||
</Router>
|
||||
</Box>
|
||||
</div>
|
||||
</CssBaseline>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
@ -1,470 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import Container from '@material-ui/core/Container';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import Tabs from '@material-ui/core/Tabs';
|
||||
import Tab from '@material-ui/core/Tab';
|
||||
import Box from '@material-ui/core/Box';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import { createTheme, Link, ThemeProvider } from '@material-ui/core';
|
||||
import Highlight from 'react-highlight';
|
||||
import { Link as RouterLink, useHistory } from 'react-router-dom';
|
||||
import { RULE_STATE, useRuleCoverage } from './utils/useRuleCoverage';
|
||||
import { useFetch } from './utils/useFetch';
|
||||
import RuleMetadata, { Version, Coverage } from './types/RuleMetadata';
|
||||
import parse, { attributesToProps, domToReact, DOMNode, Element } from 'html-react-parser';
|
||||
import VisibilityOffOutlinedIcon from '@material-ui/icons/VisibilityOffOutlined';
|
||||
|
||||
import './hljs-humanoid-light.css';
|
||||
|
||||
const PARAMETER_INTERNAL_MARGIN = 0.5;
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
'@global': {
|
||||
h1: {
|
||||
fontSize: '1.6rem',
|
||||
fontWeight: 500,
|
||||
marginTop: theme.spacing(3),
|
||||
marginBottom: theme.spacing(3)
|
||||
},
|
||||
h2: {
|
||||
color: '#0B3C62',
|
||||
fontSize: '1.2rem'
|
||||
},
|
||||
h3: {
|
||||
fontSize: '1rem',
|
||||
color: '#25699D'
|
||||
},
|
||||
hr: {
|
||||
color: '#F9F9FB'
|
||||
},
|
||||
'.sidebarblock': {
|
||||
'& .title': {
|
||||
marginTop: theme.spacing(2),
|
||||
color: '#25699D'
|
||||
},
|
||||
'& pre': {
|
||||
marginLeft: '1rem',
|
||||
marginTop: theme.spacing(PARAMETER_INTERNAL_MARGIN),
|
||||
marginBottom: theme.spacing(PARAMETER_INTERNAL_MARGIN)
|
||||
},
|
||||
'& p': {
|
||||
marginLeft: '1rem',
|
||||
marginTop: theme.spacing(PARAMETER_INTERNAL_MARGIN),
|
||||
marginBottom: theme.spacing(PARAMETER_INTERNAL_MARGIN)
|
||||
}
|
||||
}
|
||||
},
|
||||
ruleBar: {
|
||||
borderBottom: '1px solid lightgrey',
|
||||
},
|
||||
ruleid: {
|
||||
textAlign: 'center',
|
||||
marginTop: theme.spacing(3),
|
||||
marginBottom: theme.spacing(3),
|
||||
color: 'black'
|
||||
},
|
||||
ruleidLink: {
|
||||
color: 'inherit',
|
||||
},
|
||||
title: {
|
||||
textAlign: 'justify',
|
||||
marginTop: theme.spacing(4),
|
||||
marginBottom: theme.spacing(4),
|
||||
},
|
||||
avoid: {
|
||||
textDecoration: 'line-through'
|
||||
},
|
||||
coverage: {
|
||||
marginBottom: theme.spacing(3),
|
||||
},
|
||||
description: {
|
||||
textAlign: 'justify',
|
||||
// marginBottom: theme.spacing(3),
|
||||
},
|
||||
|
||||
// style used to center the tabs when there too few of them to fill the container
|
||||
tabRoot: {
|
||||
justifyContent: 'center'
|
||||
},
|
||||
tabScroller: {
|
||||
flexGrow: 0
|
||||
},
|
||||
unimplemented: {
|
||||
color: 'red'
|
||||
},
|
||||
|
||||
tab: {
|
||||
display: 'flex',
|
||||
|
||||
'&::before': {
|
||||
content: '""',
|
||||
display: 'block',
|
||||
width: theme.spacing(1),
|
||||
height: theme.spacing(1),
|
||||
marginRight: theme.spacing(1),
|
||||
borderRadius: theme.spacing(1),
|
||||
},
|
||||
|
||||
'& > .MuiTab-wrapper': {
|
||||
width: 'auto',
|
||||
}
|
||||
},
|
||||
coveredTab: {
|
||||
'&::before': {
|
||||
backgroundColor: RULE_STATE['covered'].color,
|
||||
}
|
||||
},
|
||||
targetedTab: {
|
||||
'&::before': {
|
||||
borderColor: RULE_STATE['targeted'].color,
|
||||
border: '1px solid',
|
||||
backgroundColor: 'transparent'
|
||||
}
|
||||
},
|
||||
removedTab: {
|
||||
'&::before': {
|
||||
backgroundColor: RULE_STATE['removed'].color,
|
||||
}
|
||||
},
|
||||
closedTab: {
|
||||
'&::before': {
|
||||
backgroundColor: RULE_STATE['closed'].color,
|
||||
}
|
||||
},
|
||||
deprecatedTab: {
|
||||
'&::before': {
|
||||
backgroundColor: RULE_STATE['deprecated'].color,
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
const theme = createTheme({});
|
||||
|
||||
type UsedStyles = ReturnType<typeof useStyles>;
|
||||
|
||||
const languageToJiraProject = new Map(Object.entries({
|
||||
'PYTHON': 'SONARPY',
|
||||
'ABAP': 'SONARABAP',
|
||||
'AZURE_RESOURCE_MANAGER': 'SONARIAC',
|
||||
'CFAMILY': 'CPP',
|
||||
'DART': 'DART',
|
||||
'DOCKER': 'SONARIAC',
|
||||
'JAVA': 'SONARJAVA',
|
||||
'JCL': 'SONARJCL',
|
||||
'COBOL': 'SONARCOBOL',
|
||||
'FLEX': 'SONARFLEX',
|
||||
'HTML': 'SONARHTML',
|
||||
'PHP': 'SONARPHP',
|
||||
'PLI': 'SONARPLI',
|
||||
'PLSQL': 'PLSQL',
|
||||
'RPG': 'SONARRPG',
|
||||
'APEX': 'SONARAPEX',
|
||||
'RUBY': 'SONARRUBY',
|
||||
'RUST': 'SKUNK',
|
||||
'KOTLIN': 'SONARKT',
|
||||
'SCALA': 'SONARSCALA',
|
||||
'GO': 'SONARGO',
|
||||
'SECRETS': 'SONARTEXT',
|
||||
'SWIFT': 'SONARSWIFT',
|
||||
'TSQL': 'TSQL',
|
||||
'VB6': 'VB6',
|
||||
'XML': 'SONARXML',
|
||||
'CLOUDFORMATION': 'SONARIAC',
|
||||
'TERRAFORM': 'SONARIAC',
|
||||
'KUBERNETES': 'SONARIAC',
|
||||
'TEXT': 'SONARTEXT',
|
||||
'ANSIBLE': 'SONARIAC',
|
||||
}));
|
||||
|
||||
const languageToGithubProject = new Map(Object.entries({
|
||||
'ABAP': 'sonar-abap',
|
||||
'AZURE_RESOURCE_MANAGER': 'sonar-iac',
|
||||
'CSHARP': 'sonar-dotnet',
|
||||
'DART': 'sonar-dart',
|
||||
'DOCKER': 'sonar-iac',
|
||||
'VBNET': 'sonar-dotnet',
|
||||
'JAVASCRIPT': 'SonarJS',
|
||||
'TYPESCRIPT': 'SonarJS',
|
||||
'SWIFT': 'sonar-swift',
|
||||
'KOTLIN': 'sonar-kotlin',
|
||||
'GO': 'sonar-go',
|
||||
'SCALA': 'sonar-scala',
|
||||
'RUBY': 'sonar-ruby',
|
||||
'RUST': 'sonar-rust',
|
||||
'APEX': 'sonar-apex',
|
||||
'HTML': 'sonar-html',
|
||||
'COBOL': 'sonar-cobol',
|
||||
'VB6': 'sonar-vb',
|
||||
'JAVA': 'sonar-java',
|
||||
'JCL': 'sonar-jcl',
|
||||
'PLI': 'sonar-pli',
|
||||
'CFAMILY': 'sonar-cpp',
|
||||
'CSS': 'sonar-css',
|
||||
'FLEX': 'sonar-flex',
|
||||
'PHP': 'sonar-php',
|
||||
'PLSQL': 'sonar-plsql',
|
||||
'PYTHON': 'sonar-python',
|
||||
'RPG': 'sonar-rpg',
|
||||
'TSQL': 'sonar-tsql',
|
||||
'XML': 'sonar-xml',
|
||||
'CLOUDFORMATION': 'sonar-iac',
|
||||
'TERRAFORM': 'sonar-iac',
|
||||
'KUBERNETES': 'sonar-iac',
|
||||
'SECRETS': 'sonar-text',
|
||||
'TEXT': 'sonar-text',
|
||||
'ANSIBLE': 'sonar-iac-enterprise',
|
||||
}));
|
||||
|
||||
function ticketsAndImplementationPRsLinks(ruleNumber: string, title: string, language?: string) {
|
||||
if (language) {
|
||||
const upperCaseLanguage = language.toUpperCase();
|
||||
const jiraProject = languageToJiraProject.get(upperCaseLanguage);
|
||||
const githubProject = languageToGithubProject.get(upperCaseLanguage);
|
||||
const titleWihoutQuotes = title.replace(/"/g, "'");
|
||||
|
||||
const implementationPRsLink = (
|
||||
<Link href={`https://github.com/SonarSource/${githubProject}/pulls?q=is%3Apr+"S${ruleNumber}"+OR+"RSPEC-${ruleNumber}"`}>
|
||||
Implementation Pull Requests
|
||||
</Link>
|
||||
);
|
||||
|
||||
if (jiraProject !== undefined) {
|
||||
const ticketsLink = (
|
||||
<Link href={`https://jira.sonarsource.com/issues/?jql=project%20%3D%20${jiraProject}%20AND%20(text%20~%20%22S${ruleNumber}%22%20OR%20text%20~%20%22RSPEC-${ruleNumber}%22%20OR%20text%20~%20"${titleWihoutQuotes}")`}>
|
||||
Implementation tickets on Jira
|
||||
</Link>
|
||||
);
|
||||
return {ticketsLink, implementationPRsLink};
|
||||
} else {
|
||||
const ticketsLink = (
|
||||
<Link href={`https://github.com/SonarSource/${githubProject}/issues?q=is%3Aissue+"S${ruleNumber}"+OR+"RSPEC-${ruleNumber}"`}>
|
||||
Implementation issues on GitHub
|
||||
</Link>
|
||||
);
|
||||
return {ticketsLink, implementationPRsLink};
|
||||
}
|
||||
} else {
|
||||
const ticketsLink = (<div>Select a language to see the implementation tickets</div>);
|
||||
const implementationPRsLink = (<div>Select a language to see the implementation pull requests</div>);
|
||||
return {ticketsLink, implementationPRsLink};
|
||||
}
|
||||
}
|
||||
|
||||
const RuleThemeProvider: React.FC = ({ children }) => {
|
||||
useStyles();
|
||||
return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
|
||||
}
|
||||
|
||||
interface PageMetadata {
|
||||
title: string;
|
||||
languagesTabs: JSX.Element[] | null;
|
||||
avoid: boolean;
|
||||
prUrl: string | undefined;
|
||||
branch: string;
|
||||
coverage: Coverage;
|
||||
isInQualityProfile: boolean;
|
||||
jsonString: string | undefined;
|
||||
}
|
||||
|
||||
function usePageMetadata(ruleid: string, language: string, classes: UsedStyles): PageMetadata {
|
||||
const metadataUrl = `${process.env.PUBLIC_URL}/rules/${ruleid}/${language ?? 'default'}-metadata.json`;
|
||||
let [metadataJSON, metadataError, metadataIsLoading] = useFetch<RuleMetadata>(metadataUrl);
|
||||
|
||||
let coverage: Coverage = 'Loading...';
|
||||
let title = 'Loading...';
|
||||
let avoid = false;
|
||||
let isInQualityProfile = false;
|
||||
let metadataJSONString;
|
||||
let languagesTabs = null;
|
||||
let prUrl: string | undefined = undefined;
|
||||
let branch = 'master';
|
||||
const { ruleCoverage, allLangsRuleCoverage, ruleStateInAnalyzer } = useRuleCoverage();
|
||||
if (metadataJSON && !metadataIsLoading && !metadataError) {
|
||||
title = metadataJSON.title;
|
||||
if ('prUrl' in metadataJSON) {
|
||||
prUrl = metadataJSON.prUrl;
|
||||
}
|
||||
branch = metadataJSON.branch;
|
||||
metadataJSON.languagesSupport.sort((a, b) => a.name.localeCompare(b.name));
|
||||
const ruleStates = metadataJSON.languagesSupport.map(({ name, status }) => ({
|
||||
name,
|
||||
ruleState: ruleStateInAnalyzer(name, metadataJSON!.allKeys, status)
|
||||
}));
|
||||
languagesTabs = ruleStates.map(({ name, ruleState }) => {
|
||||
const classNames = classes.tab + ' ' + (classes as any)[ruleState + 'Tab'];
|
||||
return <Tab key={name} label={name} value={name} className={classNames} />;
|
||||
});
|
||||
avoid = !ruleStates.some(({ ruleState }) => ruleState === 'covered' || ruleState === 'targeted');
|
||||
metadataJSONString = JSON.stringify(metadataJSON, null, 2);
|
||||
|
||||
const coverageMapper = (key: string, range: Version ): JSX.Element => {
|
||||
if (typeof range === 'string') {
|
||||
return (
|
||||
<li key={key} >{key}: {range}</li>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<li key={key} >Not covered for {key} anymore. Was covered from {range.since} to {range.until}.</li>
|
||||
);
|
||||
}
|
||||
};
|
||||
if (language) {
|
||||
coverage = ruleCoverage(language, metadataJSON.allKeys, coverageMapper);
|
||||
} else {
|
||||
coverage = allLangsRuleCoverage(metadataJSON.allKeys, coverageMapper);
|
||||
}
|
||||
isInQualityProfile = metadataJSON.defaultQualityProfiles.length > 0;
|
||||
}
|
||||
|
||||
if (coverage !== 'Not Covered') {
|
||||
prUrl = undefined;
|
||||
branch = 'master';
|
||||
}
|
||||
|
||||
return {
|
||||
title,
|
||||
languagesTabs,
|
||||
avoid,
|
||||
prUrl,
|
||||
branch,
|
||||
coverage,
|
||||
isInQualityProfile,
|
||||
jsonString: metadataJSONString
|
||||
};
|
||||
}
|
||||
|
||||
function getRspecPath(rspecId: string, language?: string) {
|
||||
return '/rspec#/rspec/' + rspecId;
|
||||
}
|
||||
|
||||
function useDescription(metadata: PageMetadata, ruleid: string, language?: string) {
|
||||
const editOnGithubUrl =
|
||||
`https://github.com/SonarSource/rspec/blob/${metadata.branch}/rules/${ruleid}${language ? '/' + language : ''}`;
|
||||
|
||||
function htmlReplacement(domNode: Element) {
|
||||
if (domNode.name === 'a' && domNode.attribs?.['data-rspec-id']) {
|
||||
const props = attributesToProps(domNode.attribs);
|
||||
return <a href={getRspecPath(domNode.attribs['data-rspec-id'], language)} {...props}>
|
||||
{domToReact(domNode.children)}
|
||||
</a>;
|
||||
}
|
||||
|
||||
if (domNode.name === 'code' && domNode.attribs?.['data-lang']) {
|
||||
return <Highlight className={domNode.attribs['data-lang']}>
|
||||
{domToReact(domNode.children)}
|
||||
</Highlight>;
|
||||
}
|
||||
|
||||
return undefined; // No modification.
|
||||
}
|
||||
|
||||
const descUrl = `${process.env.PUBLIC_URL}/rules/${ruleid}/${language ?? 'default'}-description.html`;
|
||||
|
||||
const [descHTML, descError, descIsLoading] = useFetch<string>(descUrl, false);
|
||||
|
||||
if (descHTML !== null && !descIsLoading && !descError) {
|
||||
return <div>
|
||||
{parse(descHTML, { replace: (d: DOMNode) => htmlReplacement(d as Element) })}
|
||||
<hr />
|
||||
<a href={editOnGithubUrl}>Edit on Github</a><br />
|
||||
<hr />
|
||||
<Highlight className='json'>{metadata.jsonString}</Highlight>
|
||||
</div>;
|
||||
}
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
export function RulePage(props: any) {
|
||||
// language can be absent
|
||||
const {ruleid, language} = props.match.params;
|
||||
document.title = ruleid;
|
||||
|
||||
const history = useHistory();
|
||||
function handleLanguageChange(event: any, lang: string) {
|
||||
history.push(`/${ruleid}/${lang}`);
|
||||
}
|
||||
|
||||
const classes = useStyles();
|
||||
|
||||
const metadata = usePageMetadata(ruleid, language, classes);
|
||||
const description = useDescription(metadata, ruleid, language);
|
||||
|
||||
let prLink = <></>;
|
||||
if (metadata.prUrl) {
|
||||
prLink = <div>
|
||||
<span className={classes.unimplemented}>Not implemented (see <a href={metadata.prUrl}>PR</a>)</span>
|
||||
</div>;
|
||||
}
|
||||
const ruleNumber = ruleid.substring(1);
|
||||
|
||||
const specificationPRsLink = (
|
||||
<Link href={`https://github.com/SonarSource/rspec/pulls?q=is%3Apr+"S${ruleNumber}"+OR+"RSPEC-${ruleNumber}"`}>
|
||||
Specification Pull Requests
|
||||
</Link>
|
||||
);
|
||||
|
||||
const {ticketsLink, implementationPRsLink} = ticketsAndImplementationPRsLinks(ruleNumber, metadata.title, language);
|
||||
const tabsValue = language ? {'value' : language} : {'value': false};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={classes.ruleBar}>
|
||||
<Container>
|
||||
<Typography variant="h2" classes={{ root: classes.ruleid }}>
|
||||
<Link className={`${classes.ruleidLink} ${metadata.avoid ? classes.avoid : ''}`}
|
||||
component={RouterLink} to={`/${ruleid}`} underline="none">{ruleid}</Link>
|
||||
</Typography>
|
||||
<Typography variant="h4" classes={{ root: classes.ruleid }}>{prLink}</Typography>
|
||||
<Tabs
|
||||
{...tabsValue}
|
||||
onChange={handleLanguageChange}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
variant="scrollable"
|
||||
scrollButtons="auto"
|
||||
classes={{ root: classes.tabRoot, scroller: classes.tabScroller }}
|
||||
>
|
||||
{metadata.languagesTabs}
|
||||
</Tabs>
|
||||
</Container>
|
||||
</div>
|
||||
|
||||
<RuleThemeProvider>
|
||||
<Container maxWidth="md">
|
||||
<h1>
|
||||
{metadata.isInQualityProfile ? <></> : <><Tooltip title="Not in any Quality Profile"><VisibilityOffOutlinedIcon /></Tooltip> </>}
|
||||
{metadata.title}
|
||||
</h1>
|
||||
<hr />
|
||||
<Box className={classes.coverage}>
|
||||
<h2>Covered Since</h2>
|
||||
<ul>
|
||||
{metadata.coverage}
|
||||
</ul>
|
||||
</Box>
|
||||
|
||||
<Box className={classes.coverage}>
|
||||
<h2>Related Tickets and Pull Requests</h2>
|
||||
<ul>
|
||||
{specificationPRsLink}
|
||||
</ul>
|
||||
<ul>
|
||||
{implementationPRsLink}
|
||||
</ul>
|
||||
<ul>
|
||||
{ticketsLink}
|
||||
</ul>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Typography component={'span'} className={classes.description}>
|
||||
{description}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Container>
|
||||
</RuleThemeProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,240 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import Card from '@material-ui/core/Card';
|
||||
import CardContent from '@material-ui/core/CardContent';
|
||||
import Chip from '@material-ui/core/Chip';
|
||||
import Table from '@material-ui/core/Table';
|
||||
import TableBody from '@material-ui/core/TableBody';
|
||||
import TableCell from '@material-ui/core/TableCell';
|
||||
import TableContainer from '@material-ui/core/TableContainer';
|
||||
import TableRow from '@material-ui/core/TableRow';
|
||||
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { Link } from '@material-ui/core';
|
||||
import { IndexedRule } from './types/IndexStore';
|
||||
import { RULE_STATE, useRuleCoverage } from './utils/useRuleCoverage';
|
||||
|
||||
const CHIP_V_MARGIN = 0.5;
|
||||
const TABLE_PADDING = 1;
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
searchHit: {
|
||||
|
||||
},
|
||||
ruleid: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between'
|
||||
},
|
||||
avoid: {
|
||||
textDecoration: 'line-through'
|
||||
},
|
||||
language: {
|
||||
marginRight: theme.spacing(1),
|
||||
marginTop: theme.spacing(2),
|
||||
},
|
||||
coveredLanguageChip: {
|
||||
marginRight: theme.spacing(1),
|
||||
marginTop: theme.spacing(CHIP_V_MARGIN),
|
||||
marginBottom: theme.spacing(CHIP_V_MARGIN),
|
||||
backgroundColor: RULE_STATE['covered'].color,
|
||||
'&:hover, &:focus': {
|
||||
backgroundColor: RULE_STATE['covered'].darker
|
||||
},
|
||||
},
|
||||
targetedLanguageChip: {
|
||||
marginRight: theme.spacing(1),
|
||||
marginTop: theme.spacing(CHIP_V_MARGIN),
|
||||
marginBottom: theme.spacing(CHIP_V_MARGIN),
|
||||
color: RULE_STATE['targeted'].color,
|
||||
borderColor: RULE_STATE['targeted'].color,
|
||||
'&:hover, &:focus': {
|
||||
color: RULE_STATE['targeted'].darker,
|
||||
borderColor: RULE_STATE['covered'].darker,
|
||||
},
|
||||
},
|
||||
removedLanguageChip: {
|
||||
marginRight: theme.spacing(1),
|
||||
marginTop: theme.spacing(CHIP_V_MARGIN),
|
||||
marginBottom: theme.spacing(CHIP_V_MARGIN),
|
||||
backgroundColor: RULE_STATE['removed'].color,
|
||||
'&:hover, &:focus': {
|
||||
backgroundColor: RULE_STATE['removed'].darker
|
||||
},
|
||||
},
|
||||
deprecatedLanguageChip: {
|
||||
marginRight: theme.spacing(1),
|
||||
marginTop: theme.spacing(CHIP_V_MARGIN),
|
||||
marginBottom: theme.spacing(CHIP_V_MARGIN),
|
||||
backgroundColor: RULE_STATE['deprecated'].color,
|
||||
'&:hover, &:focus': {
|
||||
backgroundColor: RULE_STATE['deprecated'].darker
|
||||
},
|
||||
},
|
||||
closedLanguageChip: {
|
||||
marginRight: theme.spacing(1),
|
||||
marginTop: theme.spacing(CHIP_V_MARGIN),
|
||||
marginBottom: theme.spacing(CHIP_V_MARGIN),
|
||||
backgroundColor: RULE_STATE['closed'].color,
|
||||
'&:hover, &:focus': {
|
||||
backgroundColor: RULE_STATE['closed'].darker
|
||||
},
|
||||
},
|
||||
coveredTitle: {
|
||||
borderColor: RULE_STATE['covered'].color,
|
||||
padding: theme.spacing(TABLE_PADDING)
|
||||
},
|
||||
coveredMarker: {
|
||||
padding: theme.spacing(TABLE_PADDING),
|
||||
width: '100%'
|
||||
},
|
||||
targetedTitle: {
|
||||
borderColor: RULE_STATE['covered'].color,
|
||||
padding: theme.spacing(TABLE_PADDING)
|
||||
},
|
||||
targetedMarker: {
|
||||
padding: theme.spacing(TABLE_PADDING),
|
||||
width: '100%'
|
||||
},
|
||||
removedTitle: {
|
||||
borderColor: RULE_STATE['removed'].color,
|
||||
padding: theme.spacing(TABLE_PADDING)
|
||||
},
|
||||
removedMarker: {
|
||||
padding: theme.spacing(TABLE_PADDING),
|
||||
width: '100%'
|
||||
},
|
||||
deprecatedTitle: {
|
||||
borderColor: RULE_STATE['deprecated'].color,
|
||||
padding: theme.spacing(TABLE_PADDING)
|
||||
},
|
||||
deprecatedMarker: {
|
||||
padding: theme.spacing(TABLE_PADDING),
|
||||
width: '100%'
|
||||
},
|
||||
closedTitle: {
|
||||
borderColor: RULE_STATE['closed'].color,
|
||||
padding: theme.spacing(TABLE_PADDING)
|
||||
},
|
||||
closedMarker: {
|
||||
padding: theme.spacing(TABLE_PADDING),
|
||||
width: '100%'
|
||||
}
|
||||
}));
|
||||
|
||||
type SearchHitProps = {
|
||||
data: IndexedRule
|
||||
}
|
||||
|
||||
export function SearchHit(props: SearchHitProps) {
|
||||
const { ruleStateInAnalyzer } = useRuleCoverage();
|
||||
const classes = useStyles();
|
||||
|
||||
const coveredLanguages: JSX.Element[] = [];
|
||||
const targetedLanguages: JSX.Element[] = [];
|
||||
const removedLanguages: JSX.Element[] = [];
|
||||
const deprecatedLanguages: JSX.Element[] = [];
|
||||
const closedLanguages: JSX.Element[] = [];
|
||||
|
||||
const actualLanguages = props.data.supportedLanguages.filter(l => l.name !== 'default');
|
||||
const ruleStates = actualLanguages.map(l => ({
|
||||
lang: l.name,
|
||||
ruleState: ruleStateInAnalyzer(l.name, props.data.all_keys, l.status)
|
||||
}));
|
||||
|
||||
ruleStates.forEach(({lang, ruleState}) => {
|
||||
const chip = <Link key={lang} component={RouterLink} to={`/${props.data.id}/${lang}`}
|
||||
style={{ textDecoration: 'none' }}>
|
||||
<Chip
|
||||
classes={{root: (classes as any)[ruleState + 'LanguageChip']}}
|
||||
label={lang}
|
||||
color="primary"
|
||||
variant={ruleState === 'targeted' ? 'outlined' : 'default'}
|
||||
clickable
|
||||
key="{lang}"
|
||||
/>
|
||||
</Link>;
|
||||
switch(ruleState) {
|
||||
case 'targeted':
|
||||
targetedLanguages.push(chip);
|
||||
break;
|
||||
case 'removed':
|
||||
removedLanguages.push(chip);
|
||||
break;
|
||||
case 'deprecated':
|
||||
deprecatedLanguages.push(chip);
|
||||
break;
|
||||
case 'closed':
|
||||
closedLanguages.push(chip);
|
||||
break;
|
||||
case 'covered':
|
||||
default:
|
||||
coveredLanguages.push(chip);
|
||||
break;
|
||||
}
|
||||
});
|
||||
const avoid = !ruleStates.some(({ ruleState }) => ruleState === 'targeted' || ruleState === 'covered');
|
||||
const titles = props.data.titles.map(title => (
|
||||
<Typography key={title} variant="body1" component="p" gutterBottom>
|
||||
{title}
|
||||
</Typography>
|
||||
));
|
||||
|
||||
const coveredRow = coveredLanguages.length === 0 ? <></>
|
||||
: <TableRow>
|
||||
<TableCell classes={{ root: classes.coveredTitle }}>Covered</TableCell>
|
||||
<TableCell classes={{ root: classes.coveredMarker }}>{coveredLanguages}</TableCell>
|
||||
</TableRow>;
|
||||
|
||||
const targetedRow = targetedLanguages.length === 0 ? <></>
|
||||
: <TableRow>
|
||||
<TableCell classes={{ root: classes.targetedTitle }}>Targeted</TableCell>
|
||||
<TableCell classes={{ root: classes.targetedMarker }}>{targetedLanguages}</TableCell>
|
||||
</TableRow>;
|
||||
|
||||
const deprecatedRow = deprecatedLanguages.length === 0 ? <></>
|
||||
: <TableRow>
|
||||
<TableCell classes={{ root: classes.deprecatedTitle }}>Deprecated</TableCell>
|
||||
<TableCell classes={{ root: classes.deprecatedMarker }}>{deprecatedLanguages}</TableCell>
|
||||
</TableRow>;
|
||||
|
||||
const removedRow = removedLanguages.length === 0 ? <></>
|
||||
: <TableRow>
|
||||
<TableCell classes={{ root: classes.removedTitle }}>Removed</TableCell>
|
||||
<TableCell classes={{ root: classes.removedMarker }}>{removedLanguages}</TableCell>
|
||||
</TableRow>;
|
||||
|
||||
const closedRow = closedLanguages.length === 0 ? <></>
|
||||
: <TableRow>
|
||||
<TableCell classes={{ root: classes.closedTitle }}>Closed</TableCell>
|
||||
<TableCell classes={{ root: classes.closedMarker }}>{closedLanguages}</TableCell>
|
||||
</TableRow>;
|
||||
|
||||
return (
|
||||
<Card variant="outlined" classes={{ root: classes.searchHit }}>
|
||||
<CardContent>
|
||||
<Typography key="rule-id" classes={{ root: `${classes.ruleid} ${avoid ? classes.avoid : ''}` }}
|
||||
variant="h5" component="h5" gutterBottom>
|
||||
<Link component={RouterLink} to={`/${props.data.id}`} data-testid={`search-hit-${props.data.id}`}>
|
||||
<div> Rule {props.data.id} </div>
|
||||
</Link>
|
||||
</Typography>
|
||||
{titles}
|
||||
|
||||
<TableContainer>
|
||||
<Table >
|
||||
<TableBody>
|
||||
{coveredRow}
|
||||
{targetedRow}
|
||||
{removedRow}
|
||||
{deprecatedRow}
|
||||
{closedRow}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
root: {
|
||||
// marginTop: theme.spacing(1),
|
||||
},
|
||||
searchHitBox: {
|
||||
marginBottom: theme.spacing(1),
|
||||
},
|
||||
searchBar: {
|
||||
// backgroundColor: 'white'
|
||||
borderBottom: '1px solid lightgrey',
|
||||
paddingBottom: theme.spacing(1),
|
||||
},
|
||||
searchResults: {
|
||||
marginTop: theme.spacing(3),
|
||||
},
|
||||
topRow: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between'
|
||||
},
|
||||
resultsCount: {
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
fullWidth: {
|
||||
maxWidth: '120%',
|
||||
flexBasis: '120%'
|
||||
}
|
||||
}));
|
@ -1,267 +0,0 @@
|
||||
import React, { KeyboardEvent } from 'react';
|
||||
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import Container from '@material-ui/core/Container';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import Pagination from '@material-ui/lab/Pagination';
|
||||
import Box from '@material-ui/core/Box';
|
||||
|
||||
import useStyles from './SearchPage.style';
|
||||
import { useSearch } from './utils/useSearch';
|
||||
import { useFetch } from './utils/useFetch';
|
||||
import {
|
||||
SearchParamSetter,
|
||||
useLocationSearch,
|
||||
useLocationSearchState
|
||||
} from './utils/routing';
|
||||
import { SearchHit } from './SearchHit';
|
||||
import { IndexedRule, IndexAggregates } from './types/IndexStore';
|
||||
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
function correctResultsOrder(results: IndexedRule[], query: string): IndexedRule[] {
|
||||
const upperCaseQuery = query.toLocaleUpperCase();
|
||||
const reorderedResults: IndexedRule[] = [];
|
||||
results.forEach(indexedRule => {
|
||||
if (indexedRule.all_keys.includes(upperCaseQuery)) {
|
||||
reorderedResults.unshift(indexedRule);
|
||||
} else {
|
||||
reorderedResults.push(indexedRule);
|
||||
}
|
||||
});
|
||||
return reorderedResults;
|
||||
}
|
||||
|
||||
export const SearchPage = () => {
|
||||
document.title = 'Search';
|
||||
|
||||
const classes = useStyles();
|
||||
|
||||
const pageSize = 20;
|
||||
const [query, setQuery] = useLocationSearchState('query', '');
|
||||
const history = useHistory();
|
||||
|
||||
const [ruleType, setRuleType] = useLocationSearchState('types', 'ANY');
|
||||
const allRuleTypes: Record<string,string> = {
|
||||
'BUG': 'Bug',
|
||||
'CODE_SMELL': 'Code Smell',
|
||||
'SECURITY_HOTSPOT': 'Security Hotspot',
|
||||
'VULNERABILITY': 'Vulnerability'
|
||||
};
|
||||
|
||||
const [ruleTags, setRuleTags] = useLocationSearchState<string[]>('tags', [], value => value ? value.split(',') : []);
|
||||
const [qualityProfiles, setQualityProfiles] = useLocationSearchState<string[]>('qualityProfiles', [], value => value ? value.split(',') : []);
|
||||
const [ruleLang, setLanguage] = useLocationSearchState('lang', 'ANY');
|
||||
|
||||
const [pageNumber, setPageNumber] = useLocationSearchState('page', 1, parseInt);
|
||||
const {setLocationSearch} = useLocationSearch();
|
||||
|
||||
|
||||
const {results, numberOfHits, error, loading} = useSearch(query,
|
||||
ruleType === 'ANY' ? null : ruleType,
|
||||
ruleLang === 'ANY' ? null : ruleLang,
|
||||
ruleTags,
|
||||
qualityProfiles,
|
||||
pageSize, pageNumber);
|
||||
const totalPages = numberOfHits ? Math.ceil(numberOfHits/pageSize) : 0;
|
||||
|
||||
let allRuleTags:string[] = [];
|
||||
let allLangs:string[] = [];
|
||||
let allQualityProfiles = ['Sonar way', 'Sonar way recommended'];
|
||||
const aggregatesDataUrl = `${process.env.PUBLIC_URL}/rules/rule-index-aggregates.json`;
|
||||
const [aggregatesData, aggregatesDataError, aggregatesDataIsLoading] = useFetch<IndexAggregates>(aggregatesDataUrl);
|
||||
|
||||
if (aggregatesData && !aggregatesDataIsLoading && !aggregatesDataError) {
|
||||
allRuleTags = Object.keys(aggregatesData.tags).sort();
|
||||
allLangs = Object.keys(aggregatesData.langs).sort();
|
||||
allQualityProfiles = Object.keys(aggregatesData.qualityProfiles).sort();
|
||||
}
|
||||
|
||||
let resultsDisplay: string|JSX.Element[] = 'No rule found...';
|
||||
if (loading) {
|
||||
resultsDisplay = 'Searching';
|
||||
} else if (results.length > 0) {
|
||||
resultsDisplay = correctResultsOrder(results, query).map(indexedRule => <Box key={indexedRule.id} className={classes.searchHitBox}>
|
||||
<SearchHit key={indexedRule.id} data={indexedRule}/>
|
||||
</Box>);
|
||||
}
|
||||
|
||||
const paramSetters: Record<string, SearchParamSetter<any>> = {
|
||||
types: setRuleType,
|
||||
tags: setRuleTags,
|
||||
qualityProfiles: setQualityProfiles,
|
||||
lang:setLanguage,
|
||||
query: setQuery
|
||||
};
|
||||
function handleUpdate(field: string) {
|
||||
return function(event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) {
|
||||
if (pageNumber > 1) {
|
||||
const uriSearch: Record<string, any> = {
|
||||
query: query, types: ruleType, tags: ruleTags, qualityProfiles: qualityProfiles, lang: ruleLang, page: 1
|
||||
};
|
||||
uriSearch[field] = event.target.value;
|
||||
setLocationSearch(uriSearch);
|
||||
} else {
|
||||
paramSetters[field](event.target.value, {push: false});
|
||||
}
|
||||
}
|
||||
}
|
||||
function handleKeyup(event: KeyboardEvent<HTMLInputElement>) {
|
||||
if (event.key === 'Enter') {
|
||||
const query = (event.target as HTMLTextAreaElement).value;
|
||||
if (/^(S|RSPEC-?)?\d{3,}$/i.exec(query)) {
|
||||
if (0 < results.length) {
|
||||
history.push(correctResultsOrder(results, query)[0].id);
|
||||
}
|
||||
} else if (1 === results.length) {
|
||||
history.push(results[0].id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<div className={classes.searchBar}>
|
||||
<Container maxWidth="md">
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
id="title-query"
|
||||
label="Rule Title and Description"
|
||||
placeholder="Search in rule titles and descriptions"
|
||||
fullWidth
|
||||
margin="normal"
|
||||
InputLabelProps={{
|
||||
shrink: true,
|
||||
}}
|
||||
variant="outlined"
|
||||
value={query}
|
||||
onChange={handleUpdate('query')}
|
||||
onKeyUp={handleKeyup}
|
||||
error={!!error}
|
||||
helperText={error}
|
||||
autoFocus
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} container spacing={3} className={classes.fullWidth}>
|
||||
<Grid item xs={3}>
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
label="Rule type"
|
||||
value={ruleType}
|
||||
onChange={handleUpdate('types')}
|
||||
data-testid="rule-type"
|
||||
>
|
||||
<MenuItem key="Any" value="ANY">
|
||||
Any
|
||||
</MenuItem>
|
||||
{Object.keys(allRuleTypes).map((ruleType) => (
|
||||
<MenuItem key={ruleType} value={ruleType} data-testid={`rule-type-${ruleType}`}>
|
||||
{allRuleTypes[ruleType]}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={5}>
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
size="small"
|
||||
SelectProps={{
|
||||
multiple: true,
|
||||
renderValue: (selected: any) => {
|
||||
return selected.join(', ');
|
||||
}
|
||||
}}
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
label="Rule Tags"
|
||||
value={ruleTags}
|
||||
onChange={handleUpdate('tags')}
|
||||
data-testid="rule-tags"
|
||||
>
|
||||
{allRuleTags.map((ruleTag) => (
|
||||
<MenuItem key={ruleTag} value={ruleTag} data-testid={`rule-tag-${ruleTag}`}>
|
||||
{ruleTag}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
margin="normal"
|
||||
size="small"
|
||||
variant="outlined"
|
||||
label="Language"
|
||||
value={allLangs.length === 0 ? '' : ruleLang}
|
||||
onChange={handleUpdate('lang')}
|
||||
data-testid="rule-language"
|
||||
>
|
||||
<MenuItem key="Any" value="ANY">
|
||||
Any
|
||||
</MenuItem>
|
||||
{allLangs.map((lang) => (
|
||||
<MenuItem key={lang} value={lang} data-testid={`rule-language-${lang}`}>
|
||||
{lang}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
size="small"
|
||||
SelectProps={{
|
||||
multiple: true,
|
||||
renderValue: (selected: any) => {
|
||||
return selected.join(', ');
|
||||
}
|
||||
}}
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
label="Default Quality Profiles"
|
||||
value={qualityProfiles}
|
||||
onChange={handleUpdate('qualityProfiles')}
|
||||
data-testid="rule-default-quality-profile"
|
||||
>
|
||||
{allQualityProfiles.map((qualityProfile) => (
|
||||
<MenuItem key={qualityProfile} value={qualityProfile}
|
||||
data-testid={`rule-qual-profile-${qualityProfile}`}>
|
||||
{qualityProfile}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Container>
|
||||
</div>
|
||||
<div className={classes.searchResults}>
|
||||
<Container maxWidth="md">
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Box key="total-num" className={classes.topRow}>
|
||||
<Box className={classes.resultsCount}>
|
||||
<Typography variant="subtitle1">Number of rules found: {numberOfHits}</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
{resultsDisplay}
|
||||
<Pagination count={totalPages} page={pageNumber} siblingCount={2}
|
||||
onChange={(event, value) => setPageNumber(value)}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Container>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
root: {
|
||||
flexGrow: 1,
|
||||
marginBottom: '13px', // Quickfix. For some reason the topbar overlaps on the content.
|
||||
backgroundColor: '#c72b28',
|
||||
},
|
||||
homeButton: {
|
||||
marginRight: theme.spacing(2),
|
||||
},
|
||||
title: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
unimplemented: {
|
||||
color: '#FFFFFF',
|
||||
},
|
||||
}));
|
@ -1,29 +0,0 @@
|
||||
import React from 'react';
|
||||
import AppBar from '@material-ui/core/AppBar';
|
||||
import Toolbar from '@material-ui/core/Toolbar';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import HomeIcon from '@material-ui/icons/Home';
|
||||
|
||||
import useStyles from './TopBar.style';
|
||||
|
||||
export default function TopBar() {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<AppBar position="static" className={classes.root}>
|
||||
<Toolbar>
|
||||
<IconButton edge="start" className={classes.homeButton} color="inherit" aria-label="menu" href="/rspec">
|
||||
<HomeIcon />
|
||||
</IconButton>
|
||||
<Typography variant="h6" className={classes.title}>
|
||||
SonarSource Rule Specifications
|
||||
</Typography>
|
||||
<Button href="https://github.com/SonarSource/rspec/pulls?q=is%3Aopen+is%3Apr+%22Create+rule%22">
|
||||
<span className={classes.unimplemented} > Rules under specification </span>
|
||||
</Button>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
);
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import App from '../App';
|
||||
|
||||
beforeEach(() => {
|
||||
window.history.pushState({}, 'RSPEC Search Page', '/rspec/#/rspec/');
|
||||
});
|
||||
|
||||
test('renders see the GH PR link', () => {
|
||||
const { getByText } = render(<App />);
|
||||
const linkElement = getByText(/Rules under specification/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
@ -1,230 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { Location, createMemoryHistory } from 'history';
|
||||
import { render } from '@testing-library/react';
|
||||
import { Router } from 'react-router-dom';
|
||||
|
||||
import { generateRulesDescription } from '../deployment/description';
|
||||
import { generateRulesMetadata } from '../deployment/metadata';
|
||||
import { createIndexFiles } from '../deployment/searchIndex';
|
||||
import { createFiles } from '../deployment/testutils';
|
||||
import { fetchMock, normalize, FetchResult } from '../testutils';
|
||||
import { SearchPage } from '../SearchPage';
|
||||
|
||||
// The CI system is a bit slow. Increase timeout to avoid random failures.
|
||||
jest.setTimeout(20000);
|
||||
|
||||
function readJson(filepath: string) {
|
||||
const content = fs.readFileSync(filepath);
|
||||
return JSON.parse(content.toString());
|
||||
}
|
||||
|
||||
const Paths = {
|
||||
src: '',
|
||||
dst: '',
|
||||
index: '',
|
||||
store: '',
|
||||
aggregates: '',
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
Paths.src = fs.mkdtempSync(path.join(os.tmpdir(), 'end-to-end-testing-src-'));
|
||||
Paths.dst = fs.mkdtempSync(path.join(os.tmpdir(), 'end-to-end-testing-dst-'));
|
||||
|
||||
createFiles(Paths.src, {
|
||||
'S100/rule.adoc': 'Generic content',
|
||||
'S100/metadata.json': JSON.stringify({
|
||||
title: 'Generic Rule S100 Title',
|
||||
tags: ['confusing'],
|
||||
type: 'CODE_SMELL',
|
||||
defaultQualityProfiles: [
|
||||
'Sonar way'
|
||||
],
|
||||
}),
|
||||
'S100/java/rule.adoc': 'Java Content',
|
||||
'S100/java/metadata.json': JSON.stringify({
|
||||
title: 'Java Rule S100 Title',
|
||||
status: 'ready',
|
||||
}),
|
||||
|
||||
'S200/rule.adoc': 'Generic content',
|
||||
'S200/metadata.json': JSON.stringify({
|
||||
title: 'Generic Rule S200 Title',
|
||||
tags: ['confusing'],
|
||||
type: 'CODE_SMELL',
|
||||
}),
|
||||
'S200/python/rule.adoc': 'Python content',
|
||||
'S200/python/metadata.json': JSON.stringify({
|
||||
title: 'Python Rule S200 Title',
|
||||
tags: ['pep8'],
|
||||
status: 'ready',
|
||||
}),
|
||||
'S200/java/rule.adoc': 'Java Content',
|
||||
'S200/java/metadata.json': JSON.stringify({
|
||||
title: 'Java Rule S200 Title',
|
||||
type: 'BUG',
|
||||
status: 'ready',
|
||||
}),
|
||||
|
||||
'S501/rule.adoc': 'Generic content, no active language',
|
||||
'S501/metadata.json': JSON.stringify({
|
||||
title: 'Rule S501 Rule is closed with no language-specific specification',
|
||||
type: 'CODE_SMELL',
|
||||
status: 'closed',
|
||||
}),
|
||||
});
|
||||
|
||||
generateRulesMetadata(Paths.src, Paths.dst);
|
||||
generateRulesDescription(Paths.src, Paths.dst);
|
||||
createIndexFiles(Paths.dst);
|
||||
|
||||
Paths.index = path.join(Paths.dst, 'rule-index.json');
|
||||
Paths.store = path.join(Paths.dst, 'rule-index-store.json');
|
||||
Paths.aggregates = path.join(Paths.dst, 'rule-index-aggregates.json');
|
||||
expect(fs.existsSync(Paths.index)).toBeTruthy();
|
||||
expect(fs.existsSync(Paths.store)).toBeTruthy();
|
||||
expect(fs.existsSync(Paths.aggregates)).toBeTruthy();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
// FIXME replace with fs.rm(path, { recursive: true, force: true }) when upgrading node to 14.14 or later.
|
||||
fs.rmdirSync(Paths.dst, { recursive: true });
|
||||
fs.rmdirSync(Paths.src, { recursive: true });
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
const index = readJson(Paths.index);
|
||||
const indexStore = readJson(Paths.store);
|
||||
const indexAggregates = readJson(Paths.aggregates);
|
||||
|
||||
const rootUrl = process.env.PUBLIC_URL;
|
||||
const mockUrls = {} as Record<string, FetchResult>;
|
||||
mockUrls[`${rootUrl}/rules/rule-index.json`] = { json: normalize(index) };
|
||||
mockUrls[`${rootUrl}/rules/rule-index-store.json`] = { json: normalize(indexStore) };
|
||||
mockUrls[`${rootUrl}/rules/rule-index-aggregates.json`] = { json: normalize(indexAggregates) };
|
||||
mockUrls[`${rootUrl}/covered_rules.json`] = {
|
||||
json: {
|
||||
'JAVA': { 'S100': 'ver1', 'S200': 'ver2' },
|
||||
'PY': { 'S200': 'ver1' },
|
||||
}
|
||||
};
|
||||
jest.spyOn(global, 'fetch').mockImplementation(fetchMock(mockUrls));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
(global.fetch as any).mockClear();
|
||||
});
|
||||
|
||||
const defaultUseLocation = jest.requireActual('react-router-dom').useLocation;
|
||||
let fakeLocation: Location | undefined = undefined;
|
||||
|
||||
function mockUseLocation() {
|
||||
return fakeLocation ?? defaultUseLocation();
|
||||
}
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom') as {},
|
||||
useLocation: mockUseLocation,
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fakeLocation = undefined;
|
||||
});
|
||||
|
||||
function generateFakeLocation(search: string): Location {
|
||||
return {
|
||||
pathname: '/somepath',
|
||||
search: search,
|
||||
state: null,
|
||||
hash: 'somehash',
|
||||
key: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
const history = createMemoryHistory();
|
||||
|
||||
describe('The main search page display correct results', () => {
|
||||
|
||||
test('Empty query', async () => {
|
||||
expect(fakeLocation).toBeUndefined();
|
||||
const { findByText, findAllByText } = render(<Router history={history} > <SearchPage /></Router >);
|
||||
expect(await findAllByText(/Rule Title and Description/)).toHaveLength(1 + 1); // <label> + <span>
|
||||
await findByText(/Number of rules found: 3\b/);
|
||||
await findByText(/Java Rule S100 Title/);
|
||||
await findByText(/Python Rule S200 Title/);
|
||||
await findByText(/Java Rule S200 Title/);
|
||||
await findByText(/Rule S501 Rule is closed with no language-specific specification/);
|
||||
});
|
||||
|
||||
test('Query by title: Java.', async () => {
|
||||
fakeLocation = generateFakeLocation('query=Java');
|
||||
const { findByText, findAllByText } = render(<Router history={history}> <SearchPage /></Router >);
|
||||
expect(await findAllByText(/Rule Title and Description/)).toHaveLength(1 + 1); // <label> + <span>
|
||||
await findByText(/Number of rules found: 2/);
|
||||
await findByText(/Java Rule S100 Title/);
|
||||
await findByText(/Python Rule S200 Title/);
|
||||
await findByText(/Java Rule S200 Title/);
|
||||
});
|
||||
|
||||
test('Query by title: S501.', async () => {
|
||||
fakeLocation = generateFakeLocation('query=S501');
|
||||
const { findByText, findAllByText } = render(<Router history={history}> <SearchPage /></Router >);
|
||||
expect(await findAllByText(/Rule Title and Description/)).toHaveLength(1 + 1); // <label> + <span>
|
||||
await findByText(/Number of rules found: 1/);
|
||||
await findByText(/Rule S501 Rule is closed with no language-specific specification/);
|
||||
});
|
||||
|
||||
test('Query by type: BUG', async () => {
|
||||
fakeLocation = generateFakeLocation('types=BUG');
|
||||
const { findByText, findAllByText } = render(<Router history={history}> <SearchPage /></Router >);
|
||||
expect(await findAllByText(/Rule Title and Description/)).toHaveLength(1 + 1); // <label> + <span>
|
||||
await findByText(/Number of rules found: 1/);
|
||||
await findByText(/Python Rule S200 Title/);
|
||||
await findByText(/Java Rule S200 Title/);
|
||||
});
|
||||
|
||||
test('Query by type: VULNERABILITY', async () => {
|
||||
fakeLocation = generateFakeLocation('types=VULNERABILITY');
|
||||
const { findByText, findAllByText } = render(<Router history={history}> <SearchPage /></Router >);
|
||||
expect(await findAllByText(/Rule Title and Description/)).toHaveLength(1 + 1); // <label> + <span>
|
||||
await findByText(/Number of rules found: 0/);
|
||||
});
|
||||
|
||||
test('Query by tag: confusing', async () => {
|
||||
fakeLocation = generateFakeLocation('tags=confusing');
|
||||
const { findByText, findAllByText } = render(<Router history={history}> <SearchPage /></Router >);
|
||||
expect(await findAllByText(/Rule Title and Description/)).toHaveLength(1 + 1); // <label> + <span>
|
||||
await findByText(/Number of rules found: 2/);
|
||||
await findByText(/Java Rule S100 Title/);
|
||||
await findByText(/Python Rule S200 Title/);
|
||||
await findByText(/Java Rule S200 Title/);
|
||||
});
|
||||
|
||||
test('Query by tag: confusing & pep8', async () => {
|
||||
fakeLocation = generateFakeLocation('tags=confusing,pep8');
|
||||
const { findByText, findAllByText } = render(<Router history={history}> <SearchPage /></Router >);
|
||||
expect(await findAllByText(/Rule Title and Description/)).toHaveLength(1 + 1); // <label> + <span>
|
||||
await findByText(/Number of rules found: 1/);
|
||||
await findByText(/Python Rule S200 Title/);
|
||||
await findByText(/Java Rule S200 Title/);
|
||||
});
|
||||
|
||||
test('Query by language: python', async () => {
|
||||
fakeLocation = generateFakeLocation('lang=python');
|
||||
const { findByText, findAllByText } = render(<Router history={history}> <SearchPage /></Router >);
|
||||
expect(await findAllByText(/Rule Title and Description/)).toHaveLength(1 + 1); // <label> + <span>
|
||||
await findByText(/Number of rules found: 1/);
|
||||
await findByText(/Python Rule S200 Title/);
|
||||
await findByText(/Java Rule S200 Title/);
|
||||
});
|
||||
|
||||
test('Advanced query', async () => {
|
||||
fakeLocation = generateFakeLocation('qualityProfiles=Sonar way&query=Java&tags=confusing');
|
||||
const { findByText, findAllByText } = render(<Router history={history}> <SearchPage /></Router >);
|
||||
expect(await findAllByText(/Rule Title and Description/)).toHaveLength(1 + 1); // <label> + <span>
|
||||
await findByText(/Number of rules found: 1/);
|
||||
await findByText(/Java Rule S100 Title/);
|
||||
});
|
||||
|
||||
});
|
@ -1,95 +0,0 @@
|
||||
import React from 'react';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { render } from '@testing-library/react';
|
||||
import { RulePage } from '../RulePage';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { fetchMock } from '../testutils'
|
||||
|
||||
const rulesPath = path.join(__dirname, '..', 'deployment', '__tests__', 'resources', 'metadata');
|
||||
|
||||
function readRuleFile(ruleId: string, filename: string) {
|
||||
return fs.readFileSync(path.join(rulesPath, ruleId, filename)).toString();
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
const specS1000 = readRuleFile('S1000', 'cfamily-description.html');
|
||||
const metadataS1000 = readRuleFile('S1000', 'cfamily-metadata.json');
|
||||
const specS1007 = readRuleFile('S1007', 'default-description.html');
|
||||
const metadataS1007 = readRuleFile('S1007', 'default-metadata.json');
|
||||
const specS3457 = readRuleFile('S3457', 'csharp-description.html');
|
||||
const metadataS3457 = readRuleFile('S3457', 'csharp-metadata.json');
|
||||
const rootUrl = process.env.PUBLIC_URL;
|
||||
let mockUrls: {[index: string]:any} = {};
|
||||
mockUrls[`${rootUrl}/rules/S1000/cfamily-description.html`] = {text: specS1000};
|
||||
mockUrls[`${rootUrl}/rules/S1000/cfamily-metadata.json`] = {json: JSON.parse(metadataS1000)};
|
||||
mockUrls[`${rootUrl}/rules/S1007/default-description.html`] = {text: specS1007};
|
||||
mockUrls[`${rootUrl}/rules/S1007/default-metadata.json`] = {json: JSON.parse(metadataS1007)};
|
||||
mockUrls[`${rootUrl}/rules/S3457/csharp-description.html`] = {text: specS3457};
|
||||
mockUrls[`${rootUrl}/rules/S3457/csharp-metadata.json`] = {json: JSON.parse(metadataS3457)};
|
||||
mockUrls[`${rootUrl}/rules/S3457/default-description.html`] = {text: specS3457};
|
||||
mockUrls[`${rootUrl}/rules/S3457/default-metadata.json`] = {json: JSON.parse(metadataS3457)};
|
||||
mockUrls[`${rootUrl}/covered_rules.json`] = {json:
|
||||
{'ABAP': {'S100': 'ver1', 'S200': 'ver2'},
|
||||
'CSH' : {'S3457': 'c#1'},
|
||||
'C': {'S100': 'c1', 'S234': {'since': 'c2', 'until': 'c3'}}}
|
||||
};
|
||||
jest.spyOn(global, 'fetch').mockImplementation(fetchMock(mockUrls) as jest.Mocked<typeof fetch>);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.fetch.mockClear();
|
||||
});
|
||||
|
||||
test('renders cfamily version of S1000', async () => {
|
||||
const history = createMemoryHistory();
|
||||
history.push('/rspec/#/rspec/S1000/cfamily');
|
||||
const match = {params: {ruleid: "S1000", language: "cfamily"}};
|
||||
const { findByText, asFragment } = render(<Router history={history}>
|
||||
<RulePage match={match} />
|
||||
</Router>);
|
||||
await findByText(/S1000/);
|
||||
await findByText(/Implementation tickets on Jira/);
|
||||
// some random phrase from the rule specification
|
||||
await findByText(/7-3-3 - There shall be no unnamed namespaces in header files./);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('renders C# version of S3457 (using GH for issues instead of Jira)', async () => {
|
||||
const history = createMemoryHistory();
|
||||
history.push('/rspec/#/rspec/S3457/csharp');
|
||||
const match = {params: {ruleid: "S3457", language: "csharp"}};
|
||||
const { findByText, asFragment } = render(<Router history={history}>
|
||||
<RulePage match={match} />
|
||||
</Router>);
|
||||
await findByText(/S3457/);
|
||||
await findByText(/Implementation issues on GitHub/);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('renders generic version of S3457', async () => {
|
||||
const history = createMemoryHistory();
|
||||
history.push('/rspec/#/rspec/S3457');
|
||||
const match = {params: {ruleid: "S3457"}};
|
||||
const { findAllByText, asFragment } = render(<Router history={history}>
|
||||
<RulePage match={match} />
|
||||
</Router>);
|
||||
await findAllByText(/S3457/);
|
||||
await findAllByText(/cfamily/);
|
||||
await findAllByText(/csharp/);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('renders closed rule S1007', async () => {
|
||||
const history = createMemoryHistory();
|
||||
history.push('/rspec/#/rspec/S1007');
|
||||
const match = {params: {ruleid: "S1007"}};
|
||||
const { findAllByText, asFragment } = render(<Router history={history}>
|
||||
<RulePage match={match} />
|
||||
</Router>);
|
||||
await findAllByText(/S1007/);
|
||||
await findAllByText(/cfamily/);
|
||||
await findAllByText(/closed/);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
@ -1,220 +0,0 @@
|
||||
|
||||
import path from 'path';
|
||||
import { render, waitFor, fireEvent, within } from '@testing-library/react';
|
||||
import { SearchPage } from '../SearchPage';
|
||||
import { buildIndexStore, buildSearchIndex } from '../deployment/searchIndex';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { fetchMockObject, normalize } from '../testutils';
|
||||
|
||||
// The CI system is a bit slow. Increase timeout to avoid random failures.
|
||||
jest.setTimeout(20000);
|
||||
|
||||
function genMockUrls() {
|
||||
const rulePath = path.join(__dirname, '..', 'deployment', '__tests__', 'resources', 'metadata');
|
||||
const [indexStore, indexAggregates] = buildIndexStore(rulePath);
|
||||
const searchIndex: lunr.Index = buildSearchIndex(indexStore);
|
||||
const rootUrl = process.env.PUBLIC_URL;
|
||||
let mockUrls: {[index: string]: any} = {};
|
||||
mockUrls[`${rootUrl}/rules/rule-index.json`] = {json: normalize(searchIndex)};
|
||||
mockUrls[`${rootUrl}/rules/rule-index-store.json`] = {json: normalize(indexStore)};
|
||||
mockUrls[`${rootUrl}/rules/rule-index-aggregates.json`] = {json: normalize(indexAggregates)};
|
||||
mockUrls[`${rootUrl}/covered_rules.json`] = {json:
|
||||
{'CPP': {'S1000': 'ver1', 'S987': 'ver2', 'S3457': 'ver1'},
|
||||
'C': {'S1000': 'c1', 'S234': {'since': 'c2', 'until': 'c3'}},
|
||||
'PY': {'S3457': {'since': 'p2', 'until': 'p3'}}}
|
||||
};
|
||||
return mockUrls;
|
||||
}
|
||||
|
||||
let fetchMocker = fetchMockObject(genMockUrls());
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(global, 'fetch').mockImplementation(fetchMocker.mock as jest.Mocked<typeof fetch>);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fetchMocker.reset();
|
||||
global.fetch.mockClear();
|
||||
});
|
||||
|
||||
async function renderDefaultSearchPageWithHistory() {
|
||||
const history = createMemoryHistory();
|
||||
history.push('/rspec/#/rspec/');
|
||||
const renderResult = render(<Router history={history}><SearchPage /></Router>);
|
||||
|
||||
// Finish rendering after fetching all the data
|
||||
await waitFor(() => fetchMocker.finished());
|
||||
|
||||
return {renderResult, history};
|
||||
}
|
||||
|
||||
async function renderDefaultSearchPage() {
|
||||
const { renderResult } = await renderDefaultSearchPageWithHistory();
|
||||
|
||||
expect(renderResult.queryByTestId('search-hit-S1000')).not.toBeNull();
|
||||
expect(renderResult.queryByText(/rules found: 5/i)).not.toBeNull();
|
||||
return renderResult;
|
||||
}
|
||||
|
||||
test('renders the list of all rules', async () => {
|
||||
const { findByText, asFragment } = await renderDefaultSearchPage();
|
||||
await findByText(/rules found/i);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('narrows search by title', async () => {
|
||||
const { queryByText, queryByTestId, getByRole } = await renderDefaultSearchPage();
|
||||
|
||||
// Enter a search query
|
||||
const searchBox = getByRole('textbox');
|
||||
fireEvent.change(searchBox, { target: { value: 'should not be used' } });
|
||||
|
||||
expect(queryByTestId('search-hit-S987')).not.toBeNull();
|
||||
expect(queryByTestId('search-hit-S1000')).toBeNull();
|
||||
expect(queryByTestId('search-hit-S1007')).not.toBeNull();
|
||||
expect(queryByTestId('search-hit-S3457')).not.toBeNull();
|
||||
expect(queryByText(/rules found: 3/i)).not.toBeNull();
|
||||
});
|
||||
|
||||
test('on enter navigates to the ruleid', async () => {
|
||||
const { renderResult: { getByRole }, history } = await renderDefaultSearchPageWithHistory();
|
||||
|
||||
// Enter a search query
|
||||
const searchBox = getByRole('textbox');
|
||||
fireEvent.change(searchBox, { target: { value: 'S1000' } });
|
||||
fireEvent.keyUp(searchBox, { key: 'Enter', code: 'Enter', charCode: 13});
|
||||
|
||||
await waitFor(() => fetchMocker.finished());
|
||||
expect(history.entries[history.entries.length - 1].pathname).toBe('/rspec/S1000');
|
||||
});
|
||||
|
||||
test('on enter does not navigate to the wrong ruleid', async () => {
|
||||
const { renderResult: { getByRole }, history } = await renderDefaultSearchPageWithHistory();
|
||||
|
||||
// Enter a search query
|
||||
const searchBox = getByRole('textbox');
|
||||
fireEvent.change(searchBox, { target: { value: 'S10000' } });
|
||||
fireEvent.keyUp(searchBox, { key: 'Enter', code: 'Enter', charCode: 13});
|
||||
|
||||
await waitFor(() => fetchMocker.finished());
|
||||
expect(history.entries[history.entries.length - 1].pathname).toBe('/rspec/');
|
||||
});
|
||||
|
||||
test('does nothing on keyup other than enter', async () => {
|
||||
const { renderResult: { getByRole }, history } = await renderDefaultSearchPageWithHistory();
|
||||
|
||||
// Enter a search query
|
||||
const searchBox = getByRole('textbox');
|
||||
fireEvent.change(searchBox, { target: { value: 'S1000' } });
|
||||
fireEvent.keyUp(searchBox, { key: 'A', code: 'KeyA'});
|
||||
expect(history.entries[history.entries.length - 1].pathname).toBe('/rspec/');
|
||||
});
|
||||
|
||||
test('on enter navigates to the singular result', async () => {
|
||||
const { renderResult: {queryByText, getByRole}, history } = await renderDefaultSearchPageWithHistory();
|
||||
|
||||
// Enter a search query
|
||||
const searchBox = getByRole('textbox');
|
||||
fireEvent.change(searchBox, { target: { value: 'rather validated compiler' } });
|
||||
expect(queryByText(/rules found: 1/i)).not.toBeNull();
|
||||
fireEvent.keyUp(searchBox, { key: 'Enter', code: 'Enter', charCode: 13});
|
||||
|
||||
await waitFor(() => fetchMocker.finished());
|
||||
expect(history.entries[history.entries.length - 1].pathname).toBe('/rspec/S3457');
|
||||
});
|
||||
|
||||
test('shows the exact match first', async () => {
|
||||
const { queryByText, queryByTestId, getAllByTestId, getByRole } = await renderDefaultSearchPage();
|
||||
|
||||
// Search for S1000
|
||||
const searchBox = getByRole('textbox');
|
||||
fireEvent.change(searchBox, { target: { value: 'S1000' } });
|
||||
|
||||
expect(queryByText(/rules found: 2/i)).not.toBeNull();
|
||||
expect(queryByTestId('search-hit-S1000')).not.toBeNull();
|
||||
expect(queryByTestId('search-hit-S987')).not.toBeNull();
|
||||
expect(queryByTestId('search-hit-S3457')).toBeNull();
|
||||
// Make sure S1000 comes before S987
|
||||
let hitsS1000 = getAllByTestId(/search-hit/i).map((hit) => hit.getAttribute('data-testid'));
|
||||
expect(hitsS1000.indexOf('search-hit-S1000')).toBeLessThan(hitsS1000.indexOf('search-hit-S987'));
|
||||
|
||||
// Search for S987
|
||||
fireEvent.change(searchBox, { target: { value: 'S987' } });
|
||||
|
||||
expect(queryByText(/rules found: 2/i)).not.toBeNull();
|
||||
expect(queryByTestId('search-hit-S1000')).not.toBeNull();
|
||||
expect(queryByTestId('search-hit-S987')).not.toBeNull();
|
||||
expect(queryByTestId('search-hit-S3457')).toBeNull();
|
||||
// Make sure S987 comes before S1000
|
||||
let hitsS987 = getAllByTestId(/search-hit/i).map((hit) => hit.getAttribute('data-testid'));
|
||||
expect(hitsS987.indexOf('search-hit-S987')).toBeLessThan(hitsS987.indexOf('search-hit-S1000'));
|
||||
});
|
||||
|
||||
test('narrows search by rule type', async () => {
|
||||
const { queryByText, queryByTestId, getByRole, getByTestId } = await renderDefaultSearchPage();
|
||||
|
||||
// Select the rule type
|
||||
fireEvent.mouseDown(within(getByTestId('rule-type')).getByRole('button'));
|
||||
const listbox = within(getByRole('listbox'));
|
||||
fireEvent.click(listbox.getByTestId('rule-type-CODE_SMELL'));
|
||||
|
||||
expect(queryByTestId('search-hit-S987')).toBeNull();
|
||||
expect(queryByTestId('search-hit-S1000')).not.toBeNull();
|
||||
expect(queryByTestId('search-hit-S1007')).not.toBeNull();
|
||||
expect(queryByTestId('search-hit-S3457')).not.toBeNull();
|
||||
expect(queryByText(/rules found: 3/i)).not.toBeNull();
|
||||
});
|
||||
|
||||
test('narrows search by rule tags', async () => {
|
||||
const { queryByText, queryByTestId, getByRole, getByTestId } = await renderDefaultSearchPage();
|
||||
|
||||
// Select the 'clumsy' tag
|
||||
fireEvent.mouseDown(within(getByTestId('rule-tags')).getByRole('button'));
|
||||
const listbox = within(getByRole('listbox'));
|
||||
fireEvent.click(listbox.getByTestId('rule-tag-clumsy'));
|
||||
expect(queryByText(/rules found: 2/i)).not.toBeNull();
|
||||
expect(queryByTestId('search-hit-S987')).toBeNull();
|
||||
expect(queryByTestId('search-hit-S1000')).not.toBeNull();
|
||||
expect(queryByTestId('search-hit-S3457')).not.toBeNull();
|
||||
|
||||
// Also select the 'pitfall' tag
|
||||
fireEvent.click(listbox.getByTestId('rule-tag-pitfall'));
|
||||
expect(queryByText(/rules found: 1/i)).not.toBeNull();
|
||||
expect(queryByTestId('search-hit-S987')).toBeNull();
|
||||
expect(queryByTestId('search-hit-S1000')).not.toBeNull();
|
||||
expect(queryByTestId('search-hit-S3457')).toBeNull();
|
||||
});
|
||||
|
||||
test('narrows search by language', async () => {
|
||||
const { queryByText, queryByTestId, getByRole, getByTestId } = await renderDefaultSearchPage();
|
||||
|
||||
// Select the cfamily language, should keep all the rules: they all are specified for cfamily
|
||||
fireEvent.mouseDown(within(getByTestId('rule-language')).getByRole('button'));
|
||||
const listbox = within(getByRole('listbox'));
|
||||
fireEvent.click(listbox.getByTestId('rule-language-cfamily'));
|
||||
expect(queryByText(/rules found: 4/i)).not.toBeNull();
|
||||
expect(queryByTestId('search-hit-S987')).not.toBeNull();
|
||||
expect(queryByTestId('search-hit-S1000')).not.toBeNull();
|
||||
expect(queryByTestId('search-hit-S3457')).not.toBeNull();
|
||||
|
||||
// Select the java language: should keep only S3457
|
||||
fireEvent.click(listbox.getByTestId('rule-language-java'));
|
||||
expect(queryByText(/rules found: 2/i)).not.toBeNull();
|
||||
expect(queryByTestId('search-hit-S987')).toBeNull();
|
||||
expect(queryByTestId('search-hit-S1000')).toBeNull();
|
||||
expect(queryByTestId('search-hit-S3457')).not.toBeNull();
|
||||
});
|
||||
|
||||
test('narrows search by quality profile', async () => {
|
||||
const { queryByText, queryByTestId, getByRole, getByTestId } = await renderDefaultSearchPage();
|
||||
|
||||
// Select Sonar way profile - S1000, S1007, S3457 and S3649 are in this profile
|
||||
fireEvent.mouseDown(within(getByTestId('rule-default-quality-profile')).getByRole('button'));
|
||||
const listbox = within(getByRole('listbox'));
|
||||
fireEvent.click(listbox.getByTestId('rule-qual-profile-Sonar way'));
|
||||
expect(queryByText(/rules found: 3/i)).not.toBeNull();
|
||||
expect(queryByTestId('search-hit-S987')).toBeNull();
|
||||
expect(queryByTestId('search-hit-S1000')).not.toBeNull();
|
||||
expect(queryByTestId('search-hit-S3457')).not.toBeNull();
|
||||
});
|
File diff suppressed because it is too large
Load Diff
@ -1,866 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders the list of all rules 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="makeStyles-root-1"
|
||||
>
|
||||
<div
|
||||
class="makeStyles-searchBar-3"
|
||||
>
|
||||
<div
|
||||
class="MuiContainer-root MuiContainer-maxWidthMd"
|
||||
>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-container"
|
||||
>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-item MuiGrid-grid-xs-12"
|
||||
>
|
||||
<div
|
||||
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||
>
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined Mui-focused Mui-focused"
|
||||
data-shrink="true"
|
||||
for="title-query"
|
||||
id="title-query-label"
|
||||
>
|
||||
Rule Title and Description
|
||||
</label>
|
||||
<div
|
||||
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth Mui-focused Mui-focused MuiInputBase-formControl"
|
||||
>
|
||||
<input
|
||||
aria-invalid="false"
|
||||
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||
id="title-query"
|
||||
placeholder="Search in rule titles and descriptions"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<fieldset
|
||||
aria-hidden="true"
|
||||
class="PrivateNotchedOutline-root-8 MuiOutlinedInput-notchedOutline"
|
||||
>
|
||||
<legend
|
||||
class="PrivateNotchedOutline-legendLabelled-10 PrivateNotchedOutline-legendNotched-11"
|
||||
>
|
||||
<span>
|
||||
Rule Title and Description
|
||||
</span>
|
||||
</legend>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiGrid-root makeStyles-fullWidth-7 MuiGrid-container MuiGrid-spacing-xs-3 MuiGrid-item MuiGrid-grid-xs-12"
|
||||
>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-item MuiGrid-grid-xs-3"
|
||||
>
|
||||
<div
|
||||
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||
data-testid="rule-type"
|
||||
>
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-marginDense MuiInputLabel-outlined MuiFormLabel-filled"
|
||||
data-shrink="true"
|
||||
>
|
||||
Rule type
|
||||
</label>
|
||||
<div
|
||||
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl MuiInputBase-marginDense MuiOutlinedInput-marginDense"
|
||||
>
|
||||
<div
|
||||
aria-haspopup="listbox"
|
||||
class="MuiSelect-root MuiSelect-select MuiSelect-selectMenu MuiSelect-outlined MuiInputBase-input MuiOutlinedInput-input MuiInputBase-inputMarginDense MuiOutlinedInput-inputMarginDense"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Any
|
||||
</div>
|
||||
<input
|
||||
aria-hidden="true"
|
||||
class="MuiSelect-nativeInput"
|
||||
tabindex="-1"
|
||||
value="ANY"
|
||||
/>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root MuiSelect-icon MuiSelect-iconOutlined"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M7 10l5 5 5-5z"
|
||||
/>
|
||||
</svg>
|
||||
<fieldset
|
||||
aria-hidden="true"
|
||||
class="PrivateNotchedOutline-root-8 MuiOutlinedInput-notchedOutline"
|
||||
>
|
||||
<legend
|
||||
class="PrivateNotchedOutline-legendLabelled-10 PrivateNotchedOutline-legendNotched-11"
|
||||
>
|
||||
<span>
|
||||
Rule type
|
||||
</span>
|
||||
</legend>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-item MuiGrid-grid-xs-5"
|
||||
>
|
||||
<div
|
||||
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||
data-testid="rule-tags"
|
||||
>
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-marginDense MuiInputLabel-outlined"
|
||||
data-shrink="false"
|
||||
>
|
||||
Rule Tags
|
||||
</label>
|
||||
<div
|
||||
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl MuiInputBase-marginDense MuiOutlinedInput-marginDense"
|
||||
>
|
||||
<div
|
||||
aria-haspopup="listbox"
|
||||
class="MuiSelect-root MuiSelect-select MuiSelect-selectMenu MuiSelect-outlined MuiInputBase-input MuiOutlinedInput-input MuiInputBase-inputMarginDense MuiOutlinedInput-inputMarginDense"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<span>
|
||||
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
aria-hidden="true"
|
||||
class="MuiSelect-nativeInput"
|
||||
tabindex="-1"
|
||||
value=""
|
||||
/>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root MuiSelect-icon MuiSelect-iconOutlined"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M7 10l5 5 5-5z"
|
||||
/>
|
||||
</svg>
|
||||
<fieldset
|
||||
aria-hidden="true"
|
||||
class="PrivateNotchedOutline-root-8 MuiOutlinedInput-notchedOutline"
|
||||
>
|
||||
<legend
|
||||
class="PrivateNotchedOutline-legendLabelled-10"
|
||||
>
|
||||
<span>
|
||||
Rule Tags
|
||||
</span>
|
||||
</legend>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-item MuiGrid-grid-xs-4"
|
||||
>
|
||||
<div
|
||||
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||
data-testid="rule-language"
|
||||
>
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-marginDense MuiInputLabel-outlined MuiFormLabel-filled"
|
||||
data-shrink="true"
|
||||
>
|
||||
Language
|
||||
</label>
|
||||
<div
|
||||
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl MuiInputBase-marginDense MuiOutlinedInput-marginDense"
|
||||
>
|
||||
<div
|
||||
aria-haspopup="listbox"
|
||||
class="MuiSelect-root MuiSelect-select MuiSelect-selectMenu MuiSelect-outlined MuiInputBase-input MuiOutlinedInput-input MuiInputBase-inputMarginDense MuiOutlinedInput-inputMarginDense"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Any
|
||||
</div>
|
||||
<input
|
||||
aria-hidden="true"
|
||||
class="MuiSelect-nativeInput"
|
||||
tabindex="-1"
|
||||
value="ANY"
|
||||
/>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root MuiSelect-icon MuiSelect-iconOutlined"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M7 10l5 5 5-5z"
|
||||
/>
|
||||
</svg>
|
||||
<fieldset
|
||||
aria-hidden="true"
|
||||
class="PrivateNotchedOutline-root-8 MuiOutlinedInput-notchedOutline"
|
||||
>
|
||||
<legend
|
||||
class="PrivateNotchedOutline-legendLabelled-10 PrivateNotchedOutline-legendNotched-11"
|
||||
>
|
||||
<span>
|
||||
Language
|
||||
</span>
|
||||
</legend>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-item MuiGrid-grid-xs-12"
|
||||
>
|
||||
<div
|
||||
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||
data-testid="rule-default-quality-profile"
|
||||
>
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-marginDense MuiInputLabel-outlined"
|
||||
data-shrink="false"
|
||||
>
|
||||
Default Quality Profiles
|
||||
</label>
|
||||
<div
|
||||
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl MuiInputBase-marginDense MuiOutlinedInput-marginDense"
|
||||
>
|
||||
<div
|
||||
aria-haspopup="listbox"
|
||||
class="MuiSelect-root MuiSelect-select MuiSelect-selectMenu MuiSelect-outlined MuiInputBase-input MuiOutlinedInput-input MuiInputBase-inputMarginDense MuiOutlinedInput-inputMarginDense"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<span>
|
||||
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
aria-hidden="true"
|
||||
class="MuiSelect-nativeInput"
|
||||
tabindex="-1"
|
||||
value=""
|
||||
/>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root MuiSelect-icon MuiSelect-iconOutlined"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M7 10l5 5 5-5z"
|
||||
/>
|
||||
</svg>
|
||||
<fieldset
|
||||
aria-hidden="true"
|
||||
class="PrivateNotchedOutline-root-8 MuiOutlinedInput-notchedOutline"
|
||||
>
|
||||
<legend
|
||||
class="PrivateNotchedOutline-legendLabelled-10"
|
||||
>
|
||||
<span>
|
||||
Default Quality Profiles
|
||||
</span>
|
||||
</legend>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="makeStyles-searchResults-4"
|
||||
>
|
||||
<div
|
||||
class="MuiContainer-root MuiContainer-maxWidthMd"
|
||||
>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-container MuiGrid-spacing-xs-3"
|
||||
>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-item MuiGrid-grid-xs-12"
|
||||
>
|
||||
<div
|
||||
class="MuiBox-root MuiBox-root-12 makeStyles-topRow-5"
|
||||
>
|
||||
<div
|
||||
class="MuiBox-root MuiBox-root-13 makeStyles-resultsCount-6"
|
||||
>
|
||||
<h6
|
||||
class="MuiTypography-root MuiTypography-subtitle1"
|
||||
>
|
||||
Number of rules found: 5
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiBox-root MuiBox-root-14 makeStyles-searchHitBox-2"
|
||||
>
|
||||
<div
|
||||
class="MuiPaper-root MuiCard-root makeStyles-searchHit-15 MuiPaper-outlined MuiPaper-rounded"
|
||||
>
|
||||
<div
|
||||
class="MuiCardContent-root"
|
||||
>
|
||||
<h5
|
||||
class="MuiTypography-root makeStyles-ruleid-16 MuiTypography-h5 MuiTypography-gutterBottom"
|
||||
>
|
||||
<a
|
||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorPrimary"
|
||||
data-testid="search-hit-S1000"
|
||||
href="/S1000"
|
||||
>
|
||||
<div>
|
||||
Rule S1000
|
||||
</div>
|
||||
</a>
|
||||
</h5>
|
||||
<p
|
||||
class="MuiTypography-root MuiTypography-body1 MuiTypography-gutterBottom"
|
||||
>
|
||||
Header files should not contain unnamed namespaces
|
||||
</p>
|
||||
<div
|
||||
class="MuiTableContainer-root"
|
||||
>
|
||||
<table
|
||||
class="MuiTable-root"
|
||||
>
|
||||
<tbody
|
||||
class="MuiTableBody-root"
|
||||
>
|
||||
<tr
|
||||
class="MuiTableRow-root"
|
||||
>
|
||||
<td
|
||||
class="MuiTableCell-root makeStyles-coveredTitle-24 MuiTableCell-body"
|
||||
>
|
||||
Covered
|
||||
</td>
|
||||
<td
|
||||
class="MuiTableCell-root makeStyles-coveredMarker-25 MuiTableCell-body"
|
||||
>
|
||||
<a
|
||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorPrimary"
|
||||
href="/S1000/cfamily"
|
||||
style="text-decoration: none;"
|
||||
>
|
||||
<div
|
||||
class="MuiButtonBase-root MuiChip-root makeStyles-coveredLanguageChip-19 MuiChip-colorPrimary MuiChip-clickableColorPrimary MuiChip-clickable"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="MuiChip-label"
|
||||
>
|
||||
cfamily
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiBox-root MuiBox-root-34 makeStyles-searchHitBox-2"
|
||||
>
|
||||
<div
|
||||
class="MuiPaper-root MuiCard-root makeStyles-searchHit-15 MuiPaper-outlined MuiPaper-rounded"
|
||||
>
|
||||
<div
|
||||
class="MuiCardContent-root"
|
||||
>
|
||||
<h5
|
||||
class="MuiTypography-root makeStyles-ruleid-16 makeStyles-avoid-17 MuiTypography-h5 MuiTypography-gutterBottom"
|
||||
>
|
||||
<a
|
||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorPrimary"
|
||||
data-testid="search-hit-S1007"
|
||||
href="/S1007"
|
||||
>
|
||||
<div>
|
||||
Rule S1007
|
||||
</div>
|
||||
</a>
|
||||
</h5>
|
||||
<p
|
||||
class="MuiTypography-root MuiTypography-body1 MuiTypography-gutterBottom"
|
||||
>
|
||||
When the absolute positioning of bits representing a bit-field is required, then the behaviour and packing of bit-fields shall be documented
|
||||
</p>
|
||||
<div
|
||||
class="MuiTableContainer-root"
|
||||
>
|
||||
<table
|
||||
class="MuiTable-root"
|
||||
>
|
||||
<tbody
|
||||
class="MuiTableBody-root"
|
||||
>
|
||||
<tr
|
||||
class="MuiTableRow-root"
|
||||
>
|
||||
<td
|
||||
class="MuiTableCell-root makeStyles-closedTitle-32 MuiTableCell-body"
|
||||
>
|
||||
Closed
|
||||
</td>
|
||||
<td
|
||||
class="MuiTableCell-root makeStyles-closedMarker-33 MuiTableCell-body"
|
||||
>
|
||||
<a
|
||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorPrimary"
|
||||
href="/S1007/cfamily"
|
||||
style="text-decoration: none;"
|
||||
>
|
||||
<div
|
||||
class="MuiButtonBase-root MuiChip-root makeStyles-closedLanguageChip-23 MuiChip-colorPrimary MuiChip-clickableColorPrimary MuiChip-clickable"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="MuiChip-label"
|
||||
>
|
||||
cfamily
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiBox-root MuiBox-root-35 makeStyles-searchHitBox-2"
|
||||
>
|
||||
<div
|
||||
class="MuiPaper-root MuiCard-root makeStyles-searchHit-15 MuiPaper-outlined MuiPaper-rounded"
|
||||
>
|
||||
<div
|
||||
class="MuiCardContent-root"
|
||||
>
|
||||
<h5
|
||||
class="MuiTypography-root makeStyles-ruleid-16 MuiTypography-h5 MuiTypography-gutterBottom"
|
||||
>
|
||||
<a
|
||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorPrimary"
|
||||
data-testid="search-hit-S3457"
|
||||
href="/S3457"
|
||||
>
|
||||
<div>
|
||||
Rule S3457
|
||||
</div>
|
||||
</a>
|
||||
</h5>
|
||||
<p
|
||||
class="MuiTypography-root MuiTypography-body1 MuiTypography-gutterBottom"
|
||||
>
|
||||
Composite format strings should be used correctly
|
||||
</p>
|
||||
<p
|
||||
class="MuiTypography-root MuiTypography-body1 MuiTypography-gutterBottom"
|
||||
>
|
||||
Printf-style format strings should be used correctly
|
||||
</p>
|
||||
<p
|
||||
class="MuiTypography-root MuiTypography-body1 MuiTypography-gutterBottom"
|
||||
>
|
||||
String formatting should be used correctly
|
||||
</p>
|
||||
<div
|
||||
class="MuiTableContainer-root"
|
||||
>
|
||||
<table
|
||||
class="MuiTable-root"
|
||||
>
|
||||
<tbody
|
||||
class="MuiTableBody-root"
|
||||
>
|
||||
<tr
|
||||
class="MuiTableRow-root"
|
||||
>
|
||||
<td
|
||||
class="MuiTableCell-root makeStyles-coveredTitle-24 MuiTableCell-body"
|
||||
>
|
||||
Covered
|
||||
</td>
|
||||
<td
|
||||
class="MuiTableCell-root makeStyles-coveredMarker-25 MuiTableCell-body"
|
||||
>
|
||||
<a
|
||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorPrimary"
|
||||
href="/S3457/cfamily"
|
||||
style="text-decoration: none;"
|
||||
>
|
||||
<div
|
||||
class="MuiButtonBase-root MuiChip-root makeStyles-coveredLanguageChip-19 MuiChip-colorPrimary MuiChip-clickableColorPrimary MuiChip-clickable"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="MuiChip-label"
|
||||
>
|
||||
cfamily
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
class="MuiTableRow-root"
|
||||
>
|
||||
<td
|
||||
class="MuiTableCell-root makeStyles-targetedTitle-26 MuiTableCell-body"
|
||||
>
|
||||
Targeted
|
||||
</td>
|
||||
<td
|
||||
class="MuiTableCell-root makeStyles-targetedMarker-27 MuiTableCell-body"
|
||||
>
|
||||
<a
|
||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorPrimary"
|
||||
href="/S3457/csharp"
|
||||
style="text-decoration: none;"
|
||||
>
|
||||
<div
|
||||
class="MuiButtonBase-root MuiChip-root makeStyles-targetedLanguageChip-20 MuiChip-colorPrimary MuiChip-clickableColorPrimary MuiChip-outlined MuiChip-outlinedPrimary MuiChip-clickable"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="MuiChip-label"
|
||||
>
|
||||
csharp
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
class="MuiTableRow-root"
|
||||
>
|
||||
<td
|
||||
class="MuiTableCell-root makeStyles-removedTitle-28 MuiTableCell-body"
|
||||
>
|
||||
Removed
|
||||
</td>
|
||||
<td
|
||||
class="MuiTableCell-root makeStyles-removedMarker-29 MuiTableCell-body"
|
||||
>
|
||||
<a
|
||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorPrimary"
|
||||
href="/S3457/python"
|
||||
style="text-decoration: none;"
|
||||
>
|
||||
<div
|
||||
class="MuiButtonBase-root MuiChip-root makeStyles-removedLanguageChip-21 MuiChip-colorPrimary MuiChip-clickableColorPrimary MuiChip-clickable"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="MuiChip-label"
|
||||
>
|
||||
python
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
class="MuiTableRow-root"
|
||||
>
|
||||
<td
|
||||
class="MuiTableCell-root makeStyles-closedTitle-32 MuiTableCell-body"
|
||||
>
|
||||
Closed
|
||||
</td>
|
||||
<td
|
||||
class="MuiTableCell-root makeStyles-closedMarker-33 MuiTableCell-body"
|
||||
>
|
||||
<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-23 MuiChip-colorPrimary MuiChip-clickableColorPrimary MuiChip-clickable"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="MuiChip-label"
|
||||
>
|
||||
java
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiBox-root MuiBox-root-36 makeStyles-searchHitBox-2"
|
||||
>
|
||||
<div
|
||||
class="MuiPaper-root MuiCard-root makeStyles-searchHit-15 MuiPaper-outlined MuiPaper-rounded"
|
||||
>
|
||||
<div
|
||||
class="MuiCardContent-root"
|
||||
>
|
||||
<h5
|
||||
class="MuiTypography-root makeStyles-ruleid-16 MuiTypography-h5 MuiTypography-gutterBottom"
|
||||
>
|
||||
<a
|
||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorPrimary"
|
||||
data-testid="search-hit-S3649"
|
||||
href="/S3649"
|
||||
>
|
||||
<div>
|
||||
Rule S3649
|
||||
</div>
|
||||
</a>
|
||||
</h5>
|
||||
<p
|
||||
class="MuiTypography-root MuiTypography-body1 MuiTypography-gutterBottom"
|
||||
>
|
||||
Database queries should not be vulnerable to injection attacks
|
||||
</p>
|
||||
<div
|
||||
class="MuiTableContainer-root"
|
||||
>
|
||||
<table
|
||||
class="MuiTable-root"
|
||||
>
|
||||
<tbody
|
||||
class="MuiTableBody-root"
|
||||
>
|
||||
<tr
|
||||
class="MuiTableRow-root"
|
||||
>
|
||||
<td
|
||||
class="MuiTableCell-root makeStyles-targetedTitle-26 MuiTableCell-body"
|
||||
>
|
||||
Targeted
|
||||
</td>
|
||||
<td
|
||||
class="MuiTableCell-root makeStyles-targetedMarker-27 MuiTableCell-body"
|
||||
>
|
||||
<a
|
||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorPrimary"
|
||||
href="/S3649/java"
|
||||
style="text-decoration: none;"
|
||||
>
|
||||
<div
|
||||
class="MuiButtonBase-root MuiChip-root makeStyles-targetedLanguageChip-20 MuiChip-colorPrimary MuiChip-clickableColorPrimary MuiChip-outlined MuiChip-outlinedPrimary MuiChip-clickable"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="MuiChip-label"
|
||||
>
|
||||
java
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiBox-root MuiBox-root-37 makeStyles-searchHitBox-2"
|
||||
>
|
||||
<div
|
||||
class="MuiPaper-root MuiCard-root makeStyles-searchHit-15 MuiPaper-outlined MuiPaper-rounded"
|
||||
>
|
||||
<div
|
||||
class="MuiCardContent-root"
|
||||
>
|
||||
<h5
|
||||
class="MuiTypography-root makeStyles-ruleid-16 MuiTypography-h5 MuiTypography-gutterBottom"
|
||||
>
|
||||
<a
|
||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorPrimary"
|
||||
data-testid="search-hit-S987"
|
||||
href="/S987"
|
||||
>
|
||||
<div>
|
||||
Rule S987
|
||||
</div>
|
||||
</a>
|
||||
</h5>
|
||||
<p
|
||||
class="MuiTypography-root MuiTypography-body1 MuiTypography-gutterBottom"
|
||||
>
|
||||
"<signal.h>" should not be used
|
||||
</p>
|
||||
<div
|
||||
class="MuiTableContainer-root"
|
||||
>
|
||||
<table
|
||||
class="MuiTable-root"
|
||||
>
|
||||
<tbody
|
||||
class="MuiTableBody-root"
|
||||
>
|
||||
<tr
|
||||
class="MuiTableRow-root"
|
||||
>
|
||||
<td
|
||||
class="MuiTableCell-root makeStyles-coveredTitle-24 MuiTableCell-body"
|
||||
>
|
||||
Covered
|
||||
</td>
|
||||
<td
|
||||
class="MuiTableCell-root makeStyles-coveredMarker-25 MuiTableCell-body"
|
||||
>
|
||||
<a
|
||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorPrimary"
|
||||
href="/S987/cfamily"
|
||||
style="text-decoration: none;"
|
||||
>
|
||||
<div
|
||||
class="MuiButtonBase-root MuiChip-root makeStyles-coveredLanguageChip-19 MuiChip-colorPrimary MuiChip-clickableColorPrimary MuiChip-clickable"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="MuiChip-label"
|
||||
>
|
||||
cfamily
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav
|
||||
aria-label="pagination navigation"
|
||||
class="MuiPagination-root"
|
||||
>
|
||||
<ul
|
||||
class="MuiPagination-ul"
|
||||
>
|
||||
<li>
|
||||
<button
|
||||
aria-label="Go to previous page"
|
||||
class="MuiButtonBase-root MuiPaginationItem-root MuiPaginationItem-page Mui-disabled Mui-disabled"
|
||||
disabled=""
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root MuiPaginationItem-icon"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
aria-current="true"
|
||||
aria-label="page 1"
|
||||
class="MuiButtonBase-root MuiPaginationItem-root MuiPaginationItem-page Mui-selected"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
1
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
aria-label="Go to next page"
|
||||
class="MuiButtonBase-root MuiPaginationItem-root MuiPaginationItem-page Mui-disabled Mui-disabled"
|
||||
disabled=""
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root MuiPaginationItem-icon"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
@ -1,226 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { generateOneRuleDescription, generateRulesDescription } from '../description';
|
||||
import { withTestDir, createFiles } from '../testutils';
|
||||
|
||||
describe('description generation', () => {
|
||||
test('generates html from asciidoc', () => {
|
||||
return withTestDir((srcPath) => {
|
||||
createFiles(srcPath, {
|
||||
'S100/rule.adoc': 'Generic content',
|
||||
'S100/java/rule.adoc': `
|
||||
include::../rule.adoc[]
|
||||
Specific content
|
||||
|
||||
== Test various forms of auto-link for RSPEC. S100
|
||||
|
||||
* See S100, S101,S102.
|
||||
* See RSPEC-101
|
||||
* But not S103badref.
|
||||
* This is a code literal \`+S234+\` but this isn't S567.
|
||||
|
||||
\\https://sonarsource.github.io/rspec/#/rspec/S100
|
||||
|
||||
https://sonarsource.github.io/rspec/#/rspec/S100/cfamily
|
||||
|
||||
https://sonarsource.github.io/rspec/#/rspec/S100/cfamily[]
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
int foo() {
|
||||
// No auto-links in code!
|
||||
auto S100 = 100;
|
||||
auto U100 = 100u;
|
||||
return S100 + U100;
|
||||
}
|
||||
----
|
||||
|
||||
After snippet, See S100.
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
int goo() {
|
||||
// No auto-links in code!
|
||||
// S100
|
||||
}
|
||||
----
|
||||
|
||||
more ref: RSPEC-200.
|
||||
`,
|
||||
|
||||
'S101/rule.adoc': 'Generic content',
|
||||
'S101/java/rule.adoc': `
|
||||
include::../rule.adoc[]
|
||||
Specific content
|
||||
See S100.
|
||||
`,
|
||||
|
||||
'S501/rule.adoc': 'Generic content, no active language',
|
||||
});
|
||||
|
||||
return withTestDir(async (dstPath) => {
|
||||
generateRulesDescription(srcPath, dstPath);
|
||||
|
||||
const s100Java = path.join(dstPath, 'S100', 'java-description.html');
|
||||
expect(fs.existsSync(s100Java)).toBeTruthy();
|
||||
const htmlS100Java = fs.readFileSync(s100Java);
|
||||
expect(htmlS100Java.toString()).toMatchInlineSnapshot(`
|
||||
"<div class=\\"sect1\\">
|
||||
<h2 id=\\"_description\\">Description</h2>
|
||||
<div class=\\"sectionbody\\">
|
||||
<div class=\\"paragraph\\">
|
||||
<p>Generic content
|
||||
Specific content</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class=\\"sect1\\">
|
||||
<h2 id=\\"_test_various_forms_of_auto_link_for_rspec_s100\\">Test various forms of auto-link for RSPEC. <a data-rspec-id=\\"S100\\" class=\\"rspec-auto-link\\">S100</a></h2>
|
||||
<div class=\\"sectionbody\\">
|
||||
<div class=\\"ulist\\">
|
||||
<ul>
|
||||
<li>
|
||||
<p>See <a data-rspec-id=\\"S100\\" class=\\"rspec-auto-link\\">S100</a>, <a data-rspec-id=\\"S101\\" class=\\"rspec-auto-link\\">S101</a>,<a data-rspec-id=\\"S102\\" class=\\"rspec-auto-link\\">S102</a>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>See <a data-rspec-id=\\"S101\\" class=\\"rspec-auto-link\\">RSPEC-101</a></p>
|
||||
</li>
|
||||
<li>
|
||||
<p>But not S103badref.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>This is a code literal <code>S234</code> but this isn’t <a data-rspec-id=\\"S567\\" class=\\"rspec-auto-link\\">S567</a>.</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class=\\"paragraph\\">
|
||||
<p>https://sonarsource.github.io/rspec/#/rspec/S100</p>
|
||||
</div>
|
||||
<div class=\\"paragraph\\">
|
||||
<p><a href=\\"https://sonarsource.github.io/rspec/#/rspec/S100/cfamily\\" class=\\"bare\\">https://sonarsource.github.io/rspec/#/rspec/S100/cfamily</a></p>
|
||||
</div>
|
||||
<div class=\\"paragraph\\">
|
||||
<p><a href=\\"https://sonarsource.github.io/rspec/#/rspec/S100/cfamily\\" class=\\"bare\\">https://sonarsource.github.io/rspec/#/rspec/S100/cfamily</a></p>
|
||||
</div>
|
||||
<div class=\\"listingblock\\">
|
||||
<div class=\\"content\\">
|
||||
<pre class=\\"highlight\\"><code class=\\"language-cpp\\" data-lang=\\"cpp\\">int foo() {
|
||||
// No auto-links in code!
|
||||
auto S100 = 100;
|
||||
auto U100 = 100u;
|
||||
return S100 + U100;
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class=\\"paragraph\\">
|
||||
<p>After snippet, See <a data-rspec-id=\\"S100\\" class=\\"rspec-auto-link\\">S100</a>.</p>
|
||||
</div>
|
||||
<div class=\\"listingblock\\">
|
||||
<div class=\\"content\\">
|
||||
<pre class=\\"highlight\\"><code class=\\"language-cpp\\" data-lang=\\"cpp\\">int goo() {
|
||||
// No auto-links in code!
|
||||
// S100
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class=\\"paragraph\\">
|
||||
<p>more ref: <a data-rspec-id=\\"S200\\" class=\\"rspec-auto-link\\">RSPEC-200</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>"
|
||||
`);
|
||||
|
||||
const s101Java = path.join(dstPath, 'S101', 'java-description.html');
|
||||
expect(fs.existsSync(s101Java)).toBeTruthy();
|
||||
const htmlS101Java = fs.readFileSync(s101Java);
|
||||
expect(htmlS101Java.toString()).toMatchInlineSnapshot(`
|
||||
"<div class=\\"sect1\\">
|
||||
<h2 id=\\"_description\\">Description</h2>
|
||||
<div class=\\"sectionbody\\">
|
||||
<div class=\\"paragraph\\">
|
||||
<p>Generic content
|
||||
Specific content
|
||||
See <a data-rspec-id=\\"S100\\" class=\\"rspec-auto-link\\">S100</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>"
|
||||
`);
|
||||
|
||||
const s501Default = path.join(dstPath, 'S501', 'default-description.html');
|
||||
expect(fs.existsSync(s501Default)).toBeTruthy();
|
||||
const htmlS501Default = fs.readFileSync(s501Default);
|
||||
expect(htmlS501Default.toString()).toMatchInlineSnapshot(`
|
||||
"<div class=\\"sect1\\">
|
||||
<h2 id=\\"_description\\">Description</h2>
|
||||
<div class=\\"sectionbody\\">
|
||||
<div class=\\"paragraph\\">
|
||||
<p>Generic content, no active language</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>"
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function normalizeString(str: string) {
|
||||
// Ignore \n\r vs \n differences, and ignore extra whitespace.
|
||||
return str.replace(/\r\n/g, '\n').trimEnd();
|
||||
}
|
||||
|
||||
expect.extend({
|
||||
toBeSameAsFile(received: string, expectedPath: string) {
|
||||
if (!fs.existsSync(expectedPath)) {
|
||||
return {
|
||||
message: () => `File ${expectedPath} was not found.`,
|
||||
pass: false
|
||||
};
|
||||
}
|
||||
const expected = fs.readFileSync(expectedPath).toString();
|
||||
if (normalizeString(expected) === normalizeString(received)) {
|
||||
return {
|
||||
// This message is used in case of test negation `expect(a).not.toBeSameAsFile(f)`
|
||||
message: () => `Identity check failed on ${expectedPath}.\nExpected:\n${expected}\n\nReceived:\n${received}`,
|
||||
pass: true
|
||||
};
|
||||
} else {
|
||||
const receivedPath = path.join(path.dirname(expectedPath), 'received-' + path.basename(expectedPath));
|
||||
fs.writeFileSync(receivedPath, received);
|
||||
return {
|
||||
message: () => `Identity check failed on ${expectedPath}.\nReceived file saved in ${receivedPath}`,
|
||||
pass: false
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
test('generates description for active rules', () => {
|
||||
return withTestDir(async (dstPath) => {
|
||||
generateRulesDescription(path.join(__dirname, 'resources', 'rules'), dstPath);
|
||||
const rules = fs.readdirSync(dstPath);
|
||||
expect(rules.length).toEqual(5);
|
||||
let treated = 0;
|
||||
rules.forEach(ruleDir => {
|
||||
const languages = fs.readdirSync(`${dstPath}/${ruleDir}`);
|
||||
expect(languages.length).toBeGreaterThanOrEqual(1);
|
||||
languages.forEach(file => {
|
||||
const actual = fs.readFileSync(`${dstPath}/${ruleDir}/${file}`).toString();
|
||||
const expectedPath = path.join(__dirname, 'resources', 'metadata', ruleDir, file);
|
||||
expect(actual).toBeSameAsFile(expectedPath);
|
||||
treated++;
|
||||
});
|
||||
});
|
||||
expect(treated).toBe(13);
|
||||
});
|
||||
});
|
||||
test('Generate one rule description for a rule with a "common" directory', () => {
|
||||
return withTestDir(async (dstPath) => {
|
||||
generateOneRuleDescription(path.join(__dirname, 'resources', 'rules', 'S3649'), dstPath);
|
||||
const s3649Java = path.join(dstPath, 'java-description.html');
|
||||
expect(fs.existsSync(s3649Java)).toBeTruthy();
|
||||
const actual = fs.readFileSync(s3649Java).toString();
|
||||
const expectedPath = path.join(__dirname, 'resources', 'metadata', 'S3649', 'java-description.html');
|
||||
expect(actual).toBeSameAsFile(expectedPath);
|
||||
});
|
||||
})
|
||||
});
|
@ -1,311 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { generateOneRuleMetadata, generateRulesMetadata } from '../metadata';
|
||||
import { withTestDir, createFiles } from '../testutils';
|
||||
|
||||
describe('metadata generation', () => {
|
||||
|
||||
test('language specific metadata overrides generic metadata', () => {
|
||||
return withTestDir((srcPath) => {
|
||||
createFiles(srcPath, {
|
||||
'S100/metadata.json': JSON.stringify({
|
||||
title: 'Rule S100',
|
||||
tags: ['confusing']
|
||||
}),
|
||||
'S100/java/metadata.json': JSON.stringify({
|
||||
title: 'Java Rule S100'
|
||||
}),
|
||||
'S100/python/metadata.json': JSON.stringify({
|
||||
tags: ['pep8']
|
||||
}),
|
||||
});
|
||||
return withTestDir(async (dstPath) => {
|
||||
generateRulesMetadata(srcPath, dstPath);
|
||||
const javaStrMetadata = fs.readFileSync(`${dstPath}/S100/java-metadata.json`);
|
||||
const javaMetadata = JSON.parse(javaStrMetadata.toString());
|
||||
expect(javaMetadata).toMatchObject({
|
||||
title: 'Java Rule S100',
|
||||
tags: ['confusing']
|
||||
});
|
||||
|
||||
const pythonStrMetadata = fs.readFileSync(`${dstPath}/S100/python-metadata.json`);
|
||||
const pythonMetadata = JSON.parse(pythonStrMetadata.toString());
|
||||
expect(pythonMetadata).toMatchObject({
|
||||
title: 'Rule S100',
|
||||
tags: ['pep8']
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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('computes rule types correctly', () => {
|
||||
return withTestDir((srcPath) => {
|
||||
createFiles(srcPath, {
|
||||
'S100/metadata.json': JSON.stringify({
|
||||
title: 'Rule S100',
|
||||
type: 'CODE_SMELL',
|
||||
}),
|
||||
'S100/java/metadata.json': JSON.stringify({
|
||||
title: 'Java Rule S100',
|
||||
}),
|
||||
'S100/python/metadata.json': JSON.stringify({
|
||||
type: 'CODE_SMELL',
|
||||
}),
|
||||
'S100/cfamily/metadata.json': JSON.stringify({
|
||||
type: 'BUG',
|
||||
}),
|
||||
});
|
||||
|
||||
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 cfamilyStrMetadata = fs.readFileSync(`${dstPath}/S100/cfamily-metadata.json`);
|
||||
const javaMetadata = JSON.parse(javaStrMetadata.toString());
|
||||
const pythonMetadata = JSON.parse(pythonStrMetadata.toString());
|
||||
const cfamilyMetadata = JSON.parse(cfamilyStrMetadata.toString());
|
||||
|
||||
expect(javaMetadata).toMatchObject({
|
||||
title: 'Java Rule S100',
|
||||
type: 'CODE_SMELL',
|
||||
});
|
||||
|
||||
expect(pythonMetadata).toMatchObject({
|
||||
title: 'Rule S100',
|
||||
type: 'CODE_SMELL',
|
||||
});
|
||||
|
||||
expect(cfamilyMetadata).toMatchObject({
|
||||
title: 'Rule S100',
|
||||
type: 'BUG',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('generates only requested rules if a list of rule is provided', () => {
|
||||
return withTestDir((srcPath) => {
|
||||
createFiles(srcPath, {
|
||||
'S100/java/metadata.json': JSON.stringify({
|
||||
title: 'Rule S100'
|
||||
}),
|
||||
'S200/java/metadata.json': JSON.stringify({
|
||||
title: 'Rule S200'
|
||||
}),
|
||||
});
|
||||
return withTestDir(async (dstPath) => {
|
||||
generateRulesMetadata(srcPath, dstPath, ['S100']);
|
||||
|
||||
const s100Exists = fs.existsSync(`${dstPath}/S100/java-metadata.json`);
|
||||
expect(s100Exists).toBeTruthy();
|
||||
|
||||
const s200Exists = fs.existsSync(`${dstPath}/S200/java-metadata.json`);
|
||||
expect(s200Exists).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('forwards the pr url when provided', () => {
|
||||
return withTestDir((srcPath) => {
|
||||
createFiles(srcPath, {
|
||||
'S100/java/metadata.json': JSON.stringify({
|
||||
title: 'Rule S100'
|
||||
}),
|
||||
'S200/java/metadata.json': JSON.stringify({
|
||||
title: 'Rule S200'
|
||||
}),
|
||||
});
|
||||
return withTestDir(async (dstPath) => {
|
||||
generateOneRuleMetadata(path.join(srcPath, 'S100'), path.join(dstPath, 'S100'), 'master');
|
||||
|
||||
const s100StrMetadata = fs.readFileSync(`${dstPath}/S100/java-metadata.json`);
|
||||
const s100Metadata = JSON.parse(s100StrMetadata.toString());
|
||||
expect(Object.keys(s100Metadata)).toContain('branch');
|
||||
expect(s100Metadata.branch).toEqual('master');
|
||||
expect(Object.keys(s100Metadata)).not.toContain('prUrl');
|
||||
|
||||
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 s200Metadata = JSON.parse(s200StrMetadata.toString());
|
||||
expect(Object.keys(s200Metadata)).toContain('prUrl');
|
||||
expect(s200Metadata.branch).toEqual('add-my-rule');
|
||||
expect(s200Metadata.prUrl).toEqual('https://some.pr/url');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('generates metadata for active rules', () => {
|
||||
return withTestDir(async (dstPath) => {
|
||||
generateRulesMetadata(path.join(__dirname, 'resources', 'rules'), dstPath);
|
||||
const rules = fs.readdirSync(dstPath);
|
||||
expect(rules.length).toEqual(5);
|
||||
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(13);
|
||||
});
|
||||
});
|
||||
|
||||
test('generates metadata for closed rules', () => {
|
||||
return withTestDir(srcPath => {
|
||||
createFiles(srcPath, {
|
||||
'S01/metadata.json': JSON.stringify({
|
||||
title: 'Rule is closed and has no language-specific specification',
|
||||
type: 'CODE_SMELL',
|
||||
status: 'closed',
|
||||
sqKey: 'S01',
|
||||
extra: {
|
||||
legacyKeys: ['OldS01'],
|
||||
},
|
||||
}),
|
||||
|
||||
'S02/metadata.json': JSON.stringify({
|
||||
title: 'Rule is closed and has one closed language-specific specification',
|
||||
type: 'CODE_SMELL',
|
||||
status: 'closed',
|
||||
sqKey: 'S02',
|
||||
}),
|
||||
'S02/cfamily/metadata.json': JSON.stringify({
|
||||
title: 'Language specification is closed',
|
||||
status: 'closed',
|
||||
extra: {
|
||||
legacyKeys: ['OldS02'],
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
return withTestDir(async dstPath => {
|
||||
generateRulesMetadata(srcPath, dstPath);
|
||||
|
||||
const rules = fs.readdirSync(dstPath).sort();
|
||||
expect(rules).toEqual(['S01', 'S02'].sort());
|
||||
|
||||
{
|
||||
const rule = 'S01';
|
||||
const rulePath = path.join(dstPath, rule);
|
||||
// Verify that the expected files are generated and no others
|
||||
const entries = fs.readdirSync(rulePath).sort();
|
||||
expect(entries).toEqual(['default-metadata.json'].sort());
|
||||
|
||||
// Check the top-level metadata
|
||||
const defaultFile = path.join(rulePath, 'default-metadata.json');
|
||||
const defaultData = JSON.parse(fs.readFileSync(defaultFile, 'utf8'));
|
||||
expect(defaultData).toMatchObject({
|
||||
title: 'Rule is closed and has no language-specific specification',
|
||||
type: 'CODE_SMELL',
|
||||
status: 'closed',
|
||||
languagesSupport: [],
|
||||
allKeys: ['S01', 'OldS01'],
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
const rule = 'S02';
|
||||
const rulePath = path.join(dstPath, rule);
|
||||
// Verify that the expected files are generated and no others
|
||||
const entries = fs.readdirSync(rulePath).sort();
|
||||
expect(entries).toEqual(['default-metadata.json', 'cfamily-metadata.json'].sort());
|
||||
|
||||
// Check the top-level metadata
|
||||
const defaultFile = path.join(rulePath, 'default-metadata.json');
|
||||
const defaultData = JSON.parse(fs.readFileSync(defaultFile, 'utf8'));
|
||||
// Generic data is overriden by the first language-specific specification.
|
||||
expect(defaultData).toMatchObject({
|
||||
title: 'Language specification is closed',
|
||||
type: 'CODE_SMELL',
|
||||
status: 'closed',
|
||||
languagesSupport: [{ name: 'cfamily', status: 'closed', }],
|
||||
allKeys: ['S02', 'OldS02'],
|
||||
});
|
||||
|
||||
// Check the language-specific metadata
|
||||
const cfamilyFile = path.join(rulePath, 'cfamily-metadata.json');
|
||||
const cfamilyData = JSON.parse(fs.readFileSync(cfamilyFile, 'utf8'));
|
||||
expect(cfamilyData).toMatchObject({
|
||||
title: 'Language specification is closed',
|
||||
type: 'CODE_SMELL',
|
||||
status: 'closed',
|
||||
languagesSupport: [{ name: 'cfamily', status: 'closed', }],
|
||||
allKeys: ['S02', 'OldS02'],
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('allKeys include the sqKey overridden in all languages', () => {
|
||||
return withTestDir((srcPath) => {
|
||||
createFiles(srcPath, {
|
||||
'S100/metadata.json': JSON.stringify({
|
||||
sqKey: 'S100',
|
||||
}),
|
||||
'S100/java/metadata.json': JSON.stringify({
|
||||
sqKey: 'S100-Java'
|
||||
}),
|
||||
'S100/python/metadata.json': JSON.stringify({
|
||||
sqKey: 'S100-python'
|
||||
}),
|
||||
});
|
||||
return withTestDir(async (dstPath) => {
|
||||
generateRulesMetadata(srcPath, dstPath);
|
||||
const javaStrMetadata = fs.readFileSync(`${dstPath}/S100/java-metadata.json`);
|
||||
const javaMetadata = JSON.parse(javaStrMetadata.toString());
|
||||
expect(javaMetadata).toMatchObject({
|
||||
allKeys: ['S100-Java', 'S100-python', 'S100']
|
||||
});
|
||||
|
||||
const pythonStrMetadata = fs.readFileSync(`${dstPath}/S100/python-metadata.json`);
|
||||
const pythonMetadata = JSON.parse(pythonStrMetadata.toString());
|
||||
expect(pythonMetadata).toMatchObject({
|
||||
allKeys: ['S100-Java', 'S100-python', 'S100']
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,59 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import { process_incomplete_rspecs, PullRequest } from '../pullRequestIndexing';
|
||||
import Git from 'nodegit';
|
||||
import 'setimmediate';
|
||||
|
||||
jest.mock('@octokit/rest', () => {
|
||||
return {Octokit: function() {
|
||||
this.rest = {pulls: {list: jest.fn(() => {
|
||||
return { data: [
|
||||
{ title: 'Irrelevant S832' },
|
||||
{ title: 'Create rule S343: Be friendly',
|
||||
html_url: 'https://pull.request/url',
|
||||
head: {ref: 'gh:rspec/branches/friendly-branch'},
|
||||
number: 42, },
|
||||
{ title: 'Create rule S01, and friends',
|
||||
html_url: 'example.com',
|
||||
head: {ref: 'gh:rspec/branches/some-other-branch'},
|
||||
number: 1, },
|
||||
] }
|
||||
})}};
|
||||
}};
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
Git.Clone.clone = jest.fn();
|
||||
let repo = {
|
||||
config: () => ({ setString: (name: string, value: string) => {} }),
|
||||
fetch: (remote: string) => {},
|
||||
getBranch: (name: string) => {},
|
||||
checkoutRef: (ref) => {}
|
||||
};
|
||||
Git.Clone.clone.mockReturnValueOnce(repo);
|
||||
|
||||
jest.spyOn(fs, 'existsSync').mockImplementation((fname) => {
|
||||
return fname.replace(/\\/g, '/').includes('rules/S');
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
|
||||
describe('pull request enumeration', () => {
|
||||
test('clones repository and lists only relevant PRs', () => {
|
||||
const nonExistingDir = 'not-existing-directory';
|
||||
let processedPRs = [];
|
||||
return process_incomplete_rspecs(nonExistingDir, (srcDir: string, pr: PullRequest) => {
|
||||
processedPRs.push(pr.pull_id);
|
||||
}).then(() => {
|
||||
expect(Git.Clone.clone.mock.calls.length).toBe(1);
|
||||
expect(Git.Clone.clone.mock.calls[0][1]).toBe(nonExistingDir);
|
||||
expect(processedPRs).toHaveLength(2);
|
||||
expect(processedPRs).toContain(1);
|
||||
expect(processedPRs).toContain(42);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,94 +0,0 @@
|
||||
<div class="sect1">
|
||||
<h2 id="_description">Description</h2>
|
||||
<div class="sectionbody">
|
||||
<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>
|
||||
</div>
|
||||
<div class="sect1">
|
||||
<h2 id="_noncompliant_code_example">Noncompliant Code Example</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre class="highlight"><code class="language-cpp" data-lang="cpp">// Header.hpp
|
||||
namespace // Noncompliant
|
||||
{
|
||||
extern int32_t x;
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre class="highlight"><code class="language-cpp" data-lang="cpp">// File1.cpp
|
||||
#include "Header.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
int32_t x;
|
||||
}
|
||||
|
||||
void fn_a(void)
|
||||
{
|
||||
x = 42;
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre class="highlight"><code class="language-cpp" data-lang="cpp">// 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
|
||||
{
|
||||
// I am NOT an auto link in some source code: S987.
|
||||
}
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="paragraph">
|
||||
<p>Mentioning <a data-rspec-id="S987" class="rspec-auto-link">S987</a> <a data-rspec-id="S987" class="rspec-auto-link">RSPEC-987</a> here for testing purposes.</p>
|
||||
</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>
|
@ -1,44 +0,0 @@
|
||||
{
|
||||
"title": "Header files should not contain unnamed namespaces",
|
||||
"type": "CODE_SMELL",
|
||||
"status": "ready",
|
||||
"quickfix": "unknown",
|
||||
"remediation": {
|
||||
"func": "Constant/Issue",
|
||||
"constantCost": "1h"
|
||||
},
|
||||
"tags": [
|
||||
"cert",
|
||||
"misra-c++2008",
|
||||
"clumsy",
|
||||
"pitfall"
|
||||
],
|
||||
"extra": {
|
||||
"legacyKeys": [
|
||||
"UnnamedNamespaceInHeader"
|
||||
],
|
||||
"replacementRules": []
|
||||
},
|
||||
"defaultSeverity": "Major",
|
||||
"ruleSpecification": "RSPEC-1000",
|
||||
"sqKey": "UnnamedNamespaceInHeader",
|
||||
"scope": "Main",
|
||||
"securityStandards": {
|
||||
"CERT": [
|
||||
"DCL59-CPP."
|
||||
]
|
||||
},
|
||||
"defaultQualityProfiles": [
|
||||
"Sonar way"
|
||||
],
|
||||
"languagesSupport": [
|
||||
{
|
||||
"name": "cfamily",
|
||||
"status": "ready"
|
||||
}
|
||||
],
|
||||
"allKeys": [
|
||||
"UnnamedNamespaceInHeader"
|
||||
],
|
||||
"branch": "master"
|
||||
}
|
@ -1,94 +0,0 @@
|
||||
<div class="sect1">
|
||||
<h2 id="_description">Description</h2>
|
||||
<div class="sectionbody">
|
||||
<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>
|
||||
</div>
|
||||
<div class="sect1">
|
||||
<h2 id="_noncompliant_code_example">Noncompliant Code Example</h2>
|
||||
<div class="sectionbody">
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre class="highlight"><code class="language-cpp" data-lang="cpp">// Header.hpp
|
||||
namespace // Noncompliant
|
||||
{
|
||||
extern int32_t x;
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre class="highlight"><code class="language-cpp" data-lang="cpp">// File1.cpp
|
||||
#include "Header.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
int32_t x;
|
||||
}
|
||||
|
||||
void fn_a(void)
|
||||
{
|
||||
x = 42;
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre class="highlight"><code class="language-cpp" data-lang="cpp">// 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
|
||||
{
|
||||
// I am NOT an auto link in some source code: S987.
|
||||
}
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="paragraph">
|
||||
<p>Mentioning <a data-rspec-id="S987" class="rspec-auto-link">S987</a> <a data-rspec-id="S987" class="rspec-auto-link">RSPEC-987</a> here for testing purposes.</p>
|
||||
</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>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user