Skip to content

Commit

Permalink
Refactor tool scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
vdiez committed Nov 29, 2024
1 parent f9151d0 commit 316f020
Show file tree
Hide file tree
Showing 28 changed files with 1,188 additions and 775 deletions.
372 changes: 371 additions & 1 deletion package-lock.json

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"build:cov": "mvn clean && npm run bridge:build:cov && npm run _:plugin:pre-build && npm run plugin:build",
"build:fast": "npm run bridge:build:fast && npm run _:plugin:pre-build && npm run plugin:build:fast",
"bf": "npm run build:fast",
"new-rule": "tsx tools/newRule.ts",
"generate-meta": "tsx tools/generate-meta.ts && cd packages/jsts/src/rules && npm run eslint-docs",
"new-rule": "tsx tools/new-rule.mts",
"generate-meta": "tsx tools/generate-meta.ts npm run eslint-docs",
"generate-rules-list": "node tools/generate-rules-list.js",
"ruling": "node tools/prepare-ruling.js && tsx --tsconfig packages/tsconfig.test.json --test packages/ruling/tests/projects/*.ruling.test.ts",
"ruling-parametrized": "node tools/prepare-ruling.js && SONAR_RULING_SETTINGS=../settings.js tsx --tsconfig packages/tsconfig.test.json --test packages/ruling/tests/projects/*.ruling.test.ts",
Expand All @@ -29,7 +29,7 @@
"plugin:build:fast": "mvn install -DskipTests && npm run update-ruling-data",
"pbf": "npm run plugin:build:fast",
"td": "npm --prefix typedoc/searchable-parameters-plugin run setup && npx typedoc --options typedoc/typedoc.js",
"prepare": "husky install && npm run create-rule-indexes",
"prepare": "husky install && npm run generate-meta",
"precommit": "pretty-quick --staged",
"count-rules": "node tools/count-rules.js",
"_:bridge:copy-protofiles": "cpy --flat packages/jsts/src/parsers/estree.proto sonar-plugin/bridge/src/main/protobuf && cpy --flat packages/jsts/src/parsers/estree.proto lib/jsts/src/parsers",
Expand Down Expand Up @@ -62,6 +62,7 @@
"type": "module",
"devDependencies": {
"@babel/preset-typescript": "7.26.0",
"@inquirer/prompts": "^7.1.0",
"@types/babel__preset-env": "7.9.7",
"@types/bytes": "3.1.4",
"@types/eslint": "8.56.12",
Expand Down
13 changes: 5 additions & 8 deletions packages/jsts/tests/parsers/fixtures/ast/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,15 @@
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
let a = null;
require('module-alias/register');
Expand Down
11 changes: 10 additions & 1 deletion tools/count-rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
*/
import path from 'node:path';
import fs from 'node:fs/promises';
import { pathToFileURL } from 'node:url';

/**
* Script to count the rules in SonarJS for CSS, JS and TS so that we can update the README.md
* manually. We should automatize this as we do with the eslint-plugin-sonarjs README.md
*/

const pathToJsTsRules = path.join(
import.meta.dirname,
Expand Down Expand Up @@ -65,6 +71,9 @@ async function getJsonFiles(pathToRules) {
return Promise.all(
filenames
.filter(filename => filename.endsWith('.json') && filename.length <= 'S1234.json'.length)
.map(async file => await import(path.join(pathToRules, file), { assert: { type: 'json' } })),
.map(
async file =>
await import(pathToFileURL(path.join(pathToRules, file)), { with: { type: 'json' } }),
),
);
}
148 changes: 62 additions & 86 deletions tools/generate-external-rules-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,68 @@
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
import { readFile, writeFile } from 'node:fs/promises';
import { dirname, join } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { readdir } from 'fs/promises';
import prettier from 'prettier';
import { prettier as prettierOpts } from '../package.json';
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { getAllRulesMetadata, RULES_FOLDER, writePrettyFile } from './helpers.js';

/**
* Script to be called to update the eslint-plugin-sonarjs README.md
* eslint-doc-generator will create a table in the README with all the rules contained in
* plugin. However, we only package the 'original' implementation rules.
*
* We want the README to mention what other rules are contained in SonarJS but NOT
* shipped in the ESlint plugin, so that the users can install those 3rd party plugins
* and enable those rules to get an experience as close as SonarJS but using ESLint
*
* This script will fill the README with those rules for which their meta.ts file
* exports implementation = ['external'|'decorated']
*/

const decoratedRules = [];
const externalRules = [];
(await getAllRulesMetadata()).forEach(metadata => {
if (metadata.implementation === 'decorated') {
decoratedRules.push({
sonarId: metadata.sonarKey,
rules: metadata.externalRules,
});
} else if (metadata.implementation === 'external') {
externalRules.push({
sonarId: metadata.sonarKey,
externalPlugin: metadata.externalPlugin,
externalRule: metadata.eslintId,
});
}
});

const externalContents = `| SonarJS rule ID | Rule implemented by |\n|:---|:---|\n${externalRules
.map(
rule =>
`| ${sonarCell(rule.sonarId)} | ${externalRuleCell(rule.externalPlugin, rule.externalRule)} |\n`,
)
.join('')}`;

const decoratedContents = `| SonarJS rule ID | Rules used in the SonarJS implementation |\n|:---|:---|\n${decoratedRules
.map(
rule =>
`| ${sonarCell(rule.sonarId)} | ${rule.rules.map(r => externalRuleCell(r.externalPlugin, r.externalRule)).join('<br>')} |\n`,
)
.join('')}`;

const README = join(RULES_FOLDER, 'README.md');

await writePrettyFile(
README,
(await readFile(README, 'utf8'))
.replace(
/<!--- start external rules -->.*<!--- end external rules -->/gs,
`<!--- start external rules -->\n${externalContents}\n<!--- end external rules -->`,
)
.replace(
/<!--- start decorated rules -->.*<!--- end decorated rules -->/gs,
`<!--- start decorated rules -->\n${decoratedContents}\n<!--- end decorated rules -->`,
),
);

function sonarURL(key: string) {
return `https://sonarsource.github.io/rspec/#/rspec/${key}/javascript`;
Expand Down Expand Up @@ -51,83 +107,3 @@ function externalURL(plugin: string, key: string) {
function externalRuleCell(plugin: string, key: string) {
return `[${plugin}/${key}](${externalURL(plugin, key)})`;
}

const sonarKeySorter = (a, b) =>
parseInt(a.sonarId.substring(1)) < parseInt(b.sonarId.substring(1)) ? -1 : 1;

const ruleRegex = /^S\d+$/;
const RULES_FOLDER = join(
dirname(fileURLToPath(import.meta.url)),
'..',
'packages',
'jsts',
'src',
'rules',
);

const decoratedRules = [];
const externalRules = [];
const files = await readdir(RULES_FOLDER, { withFileTypes: true });
for (const file of files) {
if (ruleRegex.test(file.name) && file.isDirectory()) {
const metadata = await import(
pathToFileURL(join(RULES_FOLDER, file.name, 'meta.js')).toString()
);
if (metadata.implementation === 'decorated') {
decoratedRules.push({
sonarId: file.name,
rules: metadata.externalRules,
});
} else if (metadata.implementation === 'external') {
externalRules.push({
sonarId: file.name,
externalPlugin: metadata.externalPlugin,
externalRule: metadata.eslintId,
});
}
}
}

externalRules.sort(sonarKeySorter);
decoratedRules.sort(sonarKeySorter);

const externalContents = `| SonarJS rule ID | Rule implemented by |\n|:---|:---|\n${externalRules
.map(
rule =>
`| ${sonarCell(rule.sonarId)} | ${externalRuleCell(rule.externalPlugin, rule.externalRule)} |\n`,
)
.join('')}`;

const decoratedContents = `| SonarJS rule ID | Rules used in the SonarJS implementation |\n|:---|:---|\n${decoratedRules
.map(
rule =>
`| ${sonarCell(rule.sonarId)} | ${rule.rules.map(r => externalRuleCell(r.externalPlugin, r.externalRule)).join('<br>')} |\n`,
)
.join('')}`;

const README = join(
dirname(fileURLToPath(import.meta.url)),
'..',
'packages',
'jsts',
'src',
'rules',
'README.md',
);

await writeFile(
README,
await prettier.format(
(await readFile(README, 'utf8'))
.replace(
/<!--- start external rules -->.*<!--- end external rules -->/gs,
`<!--- start external rules -->\n${externalContents}\n<!--- end external rules -->`,
)
.replace(
/<!--- start decorated rules -->.*<!--- end decorated rules -->/gs,
`<!--- start decorated rules -->\n${decoratedContents}\n<!--- end decorated rules -->`,
),
{ ...(prettierOpts as prettier.Options), filepath: README },
),
'utf8',
);
125 changes: 10 additions & 115 deletions tools/generate-meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,121 +14,16 @@
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
import prettier from 'prettier';
import { RuleMetaData } from '@typescript-eslint/utils/ts-eslint';
import { readdirSync, readFileSync, writeFileSync } from 'fs';
import { prettier as prettierOpts } from '../package.json';
import { join } from 'node:path/posix';
import { generateMetaForRule, listRulesDir } from './helpers.js';

const ruleRegex = /^S\d+$/;

function toUnixPath(path: string) {
return path.replace(/[\\/]+/g, '/');
}

const header = `/*
* SonarQube JavaScript Plugin
* Copyright (C) 2011-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/`;

const typeMatrix = {
CODE_SMELL: 'suggestion',
BUG: 'problem',
SECURITY_HOTSPOT: 'problem',
VULNERABILITY: 'problem',
} as const;

type rspecMeta = {
type: keyof typeof typeMatrix;
status: 'ready' | 'beta' | 'closed' | 'deprecated' | 'superseded';
title: string;
quickfix: 'covered' | undefined;
tags: string[];
};
const RULES_FOLDER = join(toUnixPath(import.meta.dirname), '../packages/jsts/src/rules/');
const METADATA_FOLDER = join(
toUnixPath(import.meta.dirname),
'../sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/',
);
const sonarWayProfileFile = join(METADATA_FOLDER, `Sonar_way_profile.json`);
const sonarWayProfile = JSON.parse(readFileSync(sonarWayProfileFile, 'utf-8'));

// Check rule rspec metadata docs in https://github.com/SonarSource/rspec/blob/master/docs/metadata.adoc
async function generateMetaForRule(ruleDir: string, ruleId: string) {
const ruleRspecMeta = JSON.parse(
readFileSync(join(METADATA_FOLDER, `${ruleId}.json`), 'utf-8'),
) as rspecMeta;
if (!typeMatrix[ruleRspecMeta.type]) {
console.log(`Type not found for rule ${ruleId}`);
}
const metadata: Omit<RuleMetaData<any, any>, 'schema' | 'messages'> = {
type: typeMatrix[ruleRspecMeta.type],
docs: {
description: ruleRspecMeta.title,
recommended: sonarWayProfile.ruleKeys.includes(ruleId),
//url: `https://github.com/SonarSource/rspec/blob/master/rules/${ruleId}/javascript/rule.adoc`,
url: `https://sonarsource.github.io/rspec/#/rspec/${ruleId}/javascript`,
requiresTypeChecking: ruleRspecMeta.tags.includes('type-dependent'),
},
};
if (ruleRspecMeta.quickfix === 'covered') {
metadata.fixable = 'code';
}
if (ruleRspecMeta.status === 'deprecated') {
metadata.deprecated = true;
}

let schema = false;
try {
schema = JSON.parse(
readFileSync(join(METADATA_FOLDER, 'schemas', `${ruleId}-schema.json`), 'utf-8'),
);
} catch {}
const metaFile = join(ruleDir, ruleId, 'generated-meta.ts');
writeFileSync(
metaFile,
await prettier.format(
`${header}
// DO NOT EDIT! This file is autogenerated by "npm run generate-meta"
export const meta = ${JSON.stringify(metadata, null, 2)};
export const sonarKey = '${ruleId}';
${
schema
? `import { JSONSchema4 } from '@typescript-eslint/utils/json-schema';
export const schema = ${JSON.stringify(schema, null, 2)} as const satisfies JSONSchema4;`
: ''
}
`,
{ ...(prettierOpts as prettier.Options), filepath: metaFile },
),
);
}

async function generateMetaForRules(ruleDir: string) {
const files = readdirSync(ruleDir, { withFileTypes: true });
for (const file of files) {
if (!ruleRegex.test(file.name)) {
continue;
}
if (file.isDirectory()) {
await generateMetaForRule(ruleDir, file.name);
}
}
/**
* Generate packages/jsts/src/rules/SXXXX/generated-meta.ts on each rule
* with data coming from the RSPEC json files. This data fills in the Rule ESLint metadata
* as well as the JSON schema files available in
* "sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/schemas"
*/
for (const file of await listRulesDir()) {
await generateMetaForRule(file);
}

await generateMetaForRules(RULES_FOLDER);
await import('./generate-rule-indexes.js');
Loading

0 comments on commit 316f020

Please sign in to comment.