sonar-autofix 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,327 @@
1
+ // sonarclient.js
2
+ const axios = require('axios');
3
+ const { exec } = require('node:child_process');
4
+ const { promisify } = require('node:util');
5
+ const execAsync = promisify(exec);
6
+
7
+ class SonarClient {
8
+ constructor(config) {
9
+ this.baseUrl = config.sonar?.host || config.baseUrl || 'http://localhost:9000';
10
+ this.token = config.sonar?.token || config.token;
11
+ this.axiosInstance = axios.create({
12
+ baseURL: this.baseUrl,
13
+ headers: {
14
+ 'Authorization': `Bearer ${this.token}`,
15
+ 'Content-Type': 'application/json'
16
+ }
17
+ });
18
+ }
19
+
20
+ // Get project analysis
21
+ async getAnalysis(projectKey) {
22
+ try {
23
+ const response = await this.axiosInstance.get(`/api/qualitygates/project_status?projectKey=${projectKey}`);
24
+ return response.data;
25
+ } catch (error) {
26
+ throw new Error(`Failed to get analysis: ${error.message}`);
27
+ }
28
+ }
29
+
30
+ // Get issues for a project - Updated to use proper endpoint
31
+ async getIssues(projectKey, severity) {
32
+ if (!projectKey) {
33
+ throw new Error('projectKey is required to fetch issues');
34
+ }
35
+ try {
36
+ var params = {
37
+ componentKeys: projectKey,
38
+ statuses: 'OPEN,CONFIRMED,REOPENED'
39
+ };
40
+ if (severity) {
41
+ params.severities = severity;
42
+ }
43
+ const response = await this.axiosInstance.get(`/api/issues/search`, {
44
+ params: params
45
+ });
46
+ return response.data;
47
+ } catch (error) {
48
+ // Provide more detailed error information for debugging
49
+ if (error.response) {
50
+ throw new Error(`Failed to get issues: ${error.message} - Status: ${error.response.status}, Data: ${JSON.stringify(error.response.data)}`);
51
+ } else {
52
+ throw new Error(`Failed to get issues: ${error.message}`);
53
+ }
54
+ }
55
+ }
56
+
57
+ // Get quality gate status - Updated endpoint
58
+ async getQualityGate(projectKey) {
59
+ try {
60
+ const response = await this.axiosInstance.get(`/api/qualitygates/project_status?projectKey=${projectKey}`);
61
+ return response.data;
62
+ } catch (error) {
63
+ // If the quality gate endpoint fails, try a different one
64
+ if (error.response && error.response.status === 404) {
65
+ console.log('Quality gate endpoint not available, trying alternative API');
66
+ // Try to get project info as fallback
67
+ const response = await this.axiosInstance.get(`/api/projects/search?projects=${projectKey}`);
68
+ return response.data;
69
+ }
70
+ throw new Error(`Failed to get quality gate: ${error.message}`);
71
+ }
72
+ }
73
+
74
+ // Get project information (fallback for quality gate)
75
+ async getProjectInfo(projectKey) {
76
+ try {
77
+ const response = await this.axiosInstance.get(`/api/projects/search?projects=${projectKey}`);
78
+ return response.data;
79
+ } catch (error) {
80
+ throw new Error(`Failed to get project info: ${error.message}`);
81
+ }
82
+ }
83
+
84
+ // Get project branches
85
+ async getProjectBranches(projectKey) {
86
+ try {
87
+ const response = await this.axiosInstance.get(`/api/project_branches/list?project=${projectKey}`);
88
+ return response.data;
89
+ } catch (error) {
90
+ throw new Error(`Failed to get project branches: ${error.message}`);
91
+ }
92
+ }
93
+
94
+ // Get project pull requests
95
+ async getProjectPullRequests(projectKey) {
96
+ try {
97
+ const response = await this.axiosInstance.get(`/api/pull_requests/list?project=${projectKey}`);
98
+ return response.data;
99
+ } catch (error) {
100
+ throw new Error(`Failed to get project pull requests: ${error.message}`);
101
+ }
102
+ }
103
+
104
+ // Get project issues by file
105
+ async getIssuesByFile(projectKey, fileKey) {
106
+ try {
107
+ const response = await this.axiosInstance.get(`/api/issues/search?projectKeys=${projectKey}&fileUuids=${fileKey}`);
108
+ return response.data;
109
+ } catch (error) {
110
+ throw new Error(`Failed to get issues by file: ${error.message}`);
111
+ }
112
+ }
113
+
114
+ // Get project issues by component
115
+ async getIssuesByComponent(projectKey, componentKey) {
116
+ try {
117
+ const response = await this.axiosInstance.get(`/api/issues/search?projectKeys=${projectKey}&componentKeys=${componentKey}`);
118
+ return response.data;
119
+ } catch (error) {
120
+ throw new Error(`Failed to get issues by component: ${error.message}`);
121
+ }
122
+ }
123
+
124
+ // Get project status (more reliable endpoint)
125
+ async getProjectStatus(projectKey) {
126
+ try {
127
+ const response = await this.axiosInstance.get(`/api/projects/search?projects=${projectKey}`);
128
+ return response.data;
129
+ } catch (error) {
130
+ throw new Error(`Failed to get project status: ${error.message}`);
131
+ }
132
+ }
133
+
134
+ // Get project metrics
135
+ async getProjectMetrics(projectKey, metricKeys) {
136
+ try {
137
+ const response = await this.axiosInstance.get(`/api/measures/component?component=${projectKey}&metricKeys=${metricKeys.join(',')}`);
138
+ return response.data;
139
+ } catch (error) {
140
+ throw new Error(`Failed to get project metrics: ${error.message}`);
141
+ }
142
+ }
143
+
144
+ // Get project issues by component (with proper error handling)
145
+ async getIssuesByComponentKey(projectKey, componentKey) {
146
+ try {
147
+ const response = await this.axiosInstance.get(`/api/issues/search?projectKeys=${projectKey}&componentKeys=${componentKey}`);
148
+ return response.data;
149
+ } catch (error) {
150
+ // If component key search fails, try with project key only
151
+ if (error.response && error.response.status === 400) {
152
+ console.log('Component search failed, falling back to project search');
153
+ const response = await this.axiosInstance.get(`/api/issues/search?projectKeys=${projectKey}`);
154
+ return response.data;
155
+ }
156
+ throw new Error(`Failed to get issues by component key: ${error.message}`);
157
+ }
158
+ }
159
+
160
+ // Get project issues by file (with proper error handling)
161
+ async getIssuesByFileKey(projectKey, fileKey) {
162
+ try {
163
+ const response = await this.axiosInstance.get(`/api/issues/search?projectKeys=${projectKey}&fileUuids=${fileKey}`);
164
+ return response.data;
165
+ } catch (error) {
166
+ // If file search fails, try with project key only
167
+ if (error.response && error.response.status === 400) {
168
+ console.log('File search failed, falling back to project search');
169
+ const response = await this.axiosInstance.get(`/api/issues/search?projectKeys=${projectKey}`);
170
+ return response.data;
171
+ }
172
+ throw new Error(`Failed to get issues by file key: ${error.message}`);
173
+ }
174
+ }
175
+
176
+ // Enhanced project information with MCP integration capability
177
+ async getEnhancedProjectInfo(projectKey) {
178
+ try {
179
+ // First, try to get project info
180
+ const projectInfo = await this.getProjectInfo(projectKey);
181
+
182
+ // Then try to get metrics if available
183
+ let metrics = null;
184
+ try {
185
+ metrics = await this.getProjectMetrics(projectKey, ['ncloc', 'bugs', 'vulnerabilities']);
186
+ } catch (metricsError) {
187
+ console.log('Metrics not available:', metricsError.message);
188
+ }
189
+
190
+ // Then try to get branches if available
191
+ let branches = null;
192
+ try {
193
+ branches = await this.getProjectBranches(projectKey);
194
+ } catch (branchesError) {
195
+ console.log('Branches not available:', branchesError.message);
196
+ }
197
+
198
+ // Then try to get pull requests if available
199
+ let pullRequests = null;
200
+ try {
201
+ pullRequests = await this.getProjectPullRequests(projectKey);
202
+ } catch (prError) {
203
+ console.log('Pull requests not available:', prError.message);
204
+ }
205
+
206
+ return {
207
+ projectInfo,
208
+ metrics,
209
+ branches,
210
+ pullRequests
211
+ };
212
+ } catch (error) {
213
+ throw new Error(`Failed to get enhanced project info: ${error.message}`);
214
+ }
215
+ }
216
+
217
+ // MCP Server integration methods
218
+ // async getMCPProjectInfo(projectKey) {
219
+ // try {
220
+ // // This method is specifically designed to work with the SonarQube MCP server
221
+ // const projectInfo = await this.getProjectInfo(projectKey);
222
+ //
223
+ // // Try to use the MCP server's enhanced capabilities
224
+ // try {
225
+ // const mcpInfo = await this.getEnhancedProjectInfo(projectKey);
226
+ // return {
227
+ // ...mcpInfo,
228
+ // source: 'sonarqube_mcp'
229
+ // };
230
+ // } catch (mcpError) {
231
+ // console.log('MCP integration not available, returning basic project info');
232
+ // return {
233
+ // projectInfo,
234
+ // source: 'sonarqube_basic'
235
+ // };
236
+ // }
237
+ // } catch (error) {
238
+ // throw new Error(`Failed to get MCP project info: ${error.message}`);
239
+ // }
240
+ // }
241
+
242
+ // Method to check if MCP server is available and working
243
+ // async testMCPConnection() {
244
+ // try {
245
+ // // Test basic project search to verify connection
246
+ // const response = await this.axiosInstance.get(`/api/projects/search?projects=test`);
247
+ // return {
248
+ // connected: true,
249
+ // status: 'MCP server is accessible',
250
+ // response: response.data
251
+ // };
252
+ // } catch (error) {
253
+ // return {
254
+ // connected: false,
255
+ // status: `MCP server connection failed: ${error.message}`,
256
+ // error: error.message
257
+ // };
258
+ // }
259
+ // }
260
+
261
+ // Method to list projects using MCP server if available
262
+ async listProjects(query = '') {
263
+ try {
264
+ // First attempt to use MCP server's search function if available
265
+ const response = await this.axiosInstance.get(`/api/projects/search?q=${query}`);
266
+ return response.data;
267
+ } catch (error) {
268
+ // Fallback to basic project search
269
+ console.log('MCP search not available, using fallback');
270
+ const response = await this.axiosInstance.get(`/api/projects/search?projects=${query}`);
271
+ return response.data;
272
+ }
273
+ }
274
+
275
+ // Run sonar-scanner analysis
276
+ async runAnalysis(projectKey) {
277
+ try {
278
+ const { stdout } = await execAsync(
279
+ `npx sonar-scanner -Dsonar.projectKey=${projectKey} -Dsonar.host.url=${this.baseUrl} -Dsonar.token=${this.token}`
280
+ );
281
+ return { success: true, output: stdout };
282
+ } catch (error) {
283
+ return { success: false, output: error.message };
284
+ }
285
+ }
286
+
287
+ // Wait for quality gate result by polling task status
288
+ async waitForQualityGate(projectKey, taskId, maxWaitMs = 120000) {
289
+ const pollInterval = 5000;
290
+ const startTime = Date.now();
291
+
292
+ while (Date.now() - startTime < maxWaitMs) {
293
+ const response = await this.axiosInstance.get(`/api/ce/task?id=${taskId}`);
294
+ const status = response.data.task.status;
295
+
296
+ if (['SUCCESS', 'FAILED', 'CANCELED'].includes(status)) {
297
+ return this.getQualityGate(projectKey);
298
+ }
299
+
300
+ await new Promise(resolve => setTimeout(resolve, pollInterval));
301
+ }
302
+
303
+ throw new Error(`Quality gate timeout after ${maxWaitMs}ms`);
304
+ }
305
+
306
+ // Get security hotspots for a project
307
+ async getHotspots(projectKey) {
308
+ try {
309
+ const response = await this.axiosInstance.get(`/api/hotspots/search?projectKey=${projectKey}`);
310
+ return response.data;
311
+ } catch (error) {
312
+ throw new Error(`Failed to get hotspots: ${error.message}`);
313
+ }
314
+ }
315
+
316
+ // Get raw source code for a component
317
+ async getSourceCode(componentKey) {
318
+ try {
319
+ const response = await this.axiosInstance.get(`/api/sources/raw?key=${componentKey}`);
320
+ return response.data;
321
+ } catch (error) {
322
+ throw new Error(`Failed to get source code: ${error.message}`);
323
+ }
324
+ }
325
+ }
326
+
327
+ module.exports = SonarClient;
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@sonar-autofix/sonar-client",
3
+ "version": "0.0.1",
4
+ "description": "Sonar client for the Sonar Autofix tool",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "keywords": [
10
+ "sonar",
11
+ "autofix",
12
+ "github",
13
+ "client"
14
+ ],
15
+ "author": "Sonar Autofix Team",
16
+ "license": "MIT",
17
+ "dependencies": {
18
+ "axios": "^1.19.0"
19
+ }
20
+ }
@@ -0,0 +1,170 @@
1
+ 'use strict';
2
+
3
+ const { exec } = require('child_process');
4
+ const { promisify } = require('util');
5
+ const path = require('path');
6
+
7
+ const execAsync = promisify(exec);
8
+
9
+ const DEFAULT_TIMEOUT = 5 * 60 * 1000; // 5 minutes
10
+
11
+ class Validator {
12
+ /**
13
+ * @param {object} config
14
+ * @param {object} config.validation
15
+ * @param {string[]} config.validation.build - Build commands to run
16
+ * @param {string[]} config.validation.lint - Lint commands to run
17
+ * @param {string[]} config.validation.tests - Test commands to run
18
+ * @param {string} [cwd] - Working directory for commands
19
+ */
20
+ constructor(config = {}, cwd = process.cwd()) {
21
+ const validation = config.validation || {};
22
+ this.commands = {
23
+ build: validation.build || ['npm run build'],
24
+ lint: validation.lint || ['npm run lint'],
25
+ tests: validation.tests || ['npm test'],
26
+ };
27
+ this.cwd = cwd;
28
+ this.timeout = config.timeout || DEFAULT_TIMEOUT;
29
+ }
30
+
31
+ /**
32
+ * Run a single shell command.
33
+ * @param {string} command
34
+ * @returns {Promise<{success: boolean, stdout: string, stderr: string, exitCode: number}>}
35
+ */
36
+ async runCommand(command) {
37
+ console.log(`[Validator] Running: ${command} (cwd: ${this.cwd})`);
38
+ try {
39
+ const { stdout, stderr } = await execAsync(command, {
40
+ cwd: this.cwd,
41
+ timeout: this.timeout,
42
+ maxBuffer: 10 * 1024 * 1024, // 10 MB
43
+ });
44
+ return { success: true, stdout: stdout || '', stderr: stderr || '', exitCode: 0 };
45
+ } catch (err) {
46
+ return {
47
+ success: false,
48
+ stdout: err.stdout || '',
49
+ stderr: err.stderr || err.message || '',
50
+ exitCode: err.code != null ? err.code : 1,
51
+ };
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Run a list of commands sequentially.
57
+ * @param {string[]} commands
58
+ * @returns {Promise<{success: boolean, output: string, errors: string[]}>}
59
+ */
60
+ async _runCommands(commands) {
61
+ const outputs = [];
62
+ const errors = [];
63
+ let success = true;
64
+
65
+ for (const cmd of commands) {
66
+ const result = await this.runCommand(cmd);
67
+ if (result.stdout) outputs.push(result.stdout);
68
+ if (!result.success) {
69
+ success = false;
70
+ errors.push(result.stderr || `Command failed: ${cmd}`);
71
+ }
72
+ }
73
+
74
+ return { success, output: outputs.join('\n'), errors };
75
+ }
76
+
77
+ /**
78
+ * Run per-file validation using the project's configured build commands.
79
+ * Instead of running individual tool checks on a single file (which ignores
80
+ * tsconfig.json, path aliases, etc.), we run the project's own build/lint
81
+ * commands so validation matches the real project setup.
82
+ * @param {string} filePath
83
+ * @returns {Promise<{success: boolean, errors: string[]}>}
84
+ */
85
+ async validateFile(filePath) {
86
+ console.log(`[Validator] Validating after changes to: ${filePath}`);
87
+ const errors = [];
88
+ let success = true;
89
+
90
+ // Run build commands — these respect tsconfig.json, path aliases, etc.
91
+ for (const cmd of this.commands.build) {
92
+ const result = await this.runCommand(cmd);
93
+ if (!result.success) {
94
+ success = false;
95
+ errors.push(result.stderr || result.stdout || `Build failed: ${cmd}`);
96
+ }
97
+ }
98
+
99
+ // Run lint commands
100
+ for (const cmd of this.commands.lint) {
101
+ const result = await this.runCommand(cmd);
102
+ if (!result.success) {
103
+ success = false;
104
+ errors.push(result.stderr || result.stdout || `Lint failed: ${cmd}`);
105
+ }
106
+ }
107
+
108
+ return { success, errors };
109
+ }
110
+
111
+ /**
112
+ * Run all configured build commands sequentially.
113
+ * @returns {Promise<{success: boolean, output: string, errors: string[]}>}
114
+ */
115
+ async runBuild() {
116
+ console.log('[Validator] Running build commands...');
117
+ return this._runCommands(this.commands.build);
118
+ }
119
+
120
+ /**
121
+ * Run all configured lint commands.
122
+ * @returns {Promise<{success: boolean, output: string, errors: string[]}>}
123
+ */
124
+ async runLint() {
125
+ console.log('[Validator] Running lint commands...');
126
+ return this._runCommands(this.commands.lint);
127
+ }
128
+
129
+ /**
130
+ * Run all configured test commands.
131
+ * @returns {Promise<{success: boolean, output: string, errors: string[]}>}
132
+ */
133
+ async runTests() {
134
+ console.log('[Validator] Running test commands...');
135
+ return this._runCommands(this.commands.tests);
136
+ }
137
+
138
+ /**
139
+ * Run build, lint, and tests in sequence.
140
+ * @returns {Promise<{success: boolean, results: {build: object, lint: object, tests: object}}>}
141
+ */
142
+ async validateAll() {
143
+ console.log('[Validator] Running full validation...');
144
+ const build = await this.runBuild();
145
+ const lint = await this.runLint();
146
+ const tests = await this.runTests();
147
+
148
+ return {
149
+ success: build.success && lint.success && tests.success,
150
+ results: { build, lint, tests },
151
+ };
152
+ }
153
+
154
+ /**
155
+ * Run per-batch validations (build + tests).
156
+ * @returns {Promise<{success: boolean, results: {build: object, tests: object}}>}
157
+ */
158
+ async validateBatch() {
159
+ console.log('[Validator] Running batch validation (build + tests)...');
160
+ const build = await this.runBuild();
161
+ const tests = await this.runTests();
162
+
163
+ return {
164
+ success: build.success && tests.success,
165
+ results: { build, tests },
166
+ };
167
+ }
168
+ }
169
+
170
+ module.exports = Validator;
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@sonar-autofix/validator",
3
+ "version": "0.0.1",
4
+ "description": "Validator for the Sonar Autofix tool",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "keywords": [
10
+ "sonar",
11
+ "autofix",
12
+ "github",
13
+ "validator"
14
+ ],
15
+ "author": "Sonar Autofix Team",
16
+ "license": "MIT"
17
+ }
@@ -0,0 +1,51 @@
1
+ You are an expert software engineer tasked with fixing code quality issues identified by SonarQube/SonarCloud.
2
+
3
+ ## Instructions
4
+
5
+ You will be given:
6
+ 1. The full contents of a source file
7
+ 2. A list of issues found by Sonar analysis, each with a rule, severity, message, and line number
8
+
9
+ Your task is to fix ALL the listed issues in the file while following these rules:
10
+
11
+ - Modify only the supplied file.
12
+ - Do not rename public APIs.
13
+ - Preserve formatting and code style.
14
+ - Do not introduce unrelated changes.
15
+ - Return the complete file content with all fixes applied.
16
+ - If an issue cannot be safely fixed without risking breaking changes, leave it as-is and add a comment explaining why.
17
+
18
+ ## File
19
+
20
+ **Path:** {{filePath}}
21
+
22
+ ```
23
+ {{fileContent}}
24
+ ```
25
+
26
+ ## Issues to Fix
27
+
28
+ {{#each issues}}
29
+ ### Issue {{@index}}
30
+ - **Rule:** {{this.rule}}
31
+ - **Severity:** {{this.severity}}
32
+ - **Type:** {{this.type}}
33
+ - **Message:** {{this.message}}
34
+ - **Line:** {{this.line}}
35
+ {{/each}}
36
+
37
+ {{#if codingGuidelines}}
38
+ ## Coding Guidelines
39
+
40
+ {{codingGuidelines}}
41
+ {{/if}}
42
+
43
+ {{#if projectInstructions}}
44
+ ## Project Instructions
45
+
46
+ {{projectInstructions}}
47
+ {{/if}}
48
+
49
+ ## Response Format
50
+
51
+ Return ONLY the complete fixed file content, wrapped in a code block with the appropriate language identifier. Do not include any explanations before or after the code block.
@@ -0,0 +1,42 @@
1
+ You are an expert software engineer. A previous attempt to fix code quality issues failed validation.
2
+
3
+ ## Instructions
4
+
5
+ Your previous fix attempt caused the following validation errors. Please fix the file again, addressing both the original Sonar issues AND the validation errors.
6
+
7
+ Rules:
8
+ - Modify only the supplied file.
9
+ - Do not rename public APIs.
10
+ - Preserve formatting and code style.
11
+ - Do not introduce unrelated changes.
12
+ - Return the complete file content with all fixes applied.
13
+ - Pay special attention to the validation errors — these must be resolved.
14
+
15
+ ## File
16
+
17
+ **Path:** {{filePath}}
18
+
19
+ ```
20
+ {{fileContent}}
21
+ ```
22
+
23
+ ## Original Sonar Issues
24
+
25
+ {{#each issues}}
26
+ ### Issue {{@index}}
27
+ - **Rule:** {{this.rule}}
28
+ - **Severity:** {{this.severity}}
29
+ - **Type:** {{this.type}}
30
+ - **Message:** {{this.message}}
31
+ - **Line:** {{this.line}}
32
+ {{/each}}
33
+
34
+ ## Validation Errors from Previous Attempt
35
+
36
+ ```
37
+ {{validationErrors}}
38
+ ```
39
+
40
+ ## Response Format
41
+
42
+ Return ONLY the complete fixed file content, wrapped in a code block with the appropriate language identifier. Do not include any explanations before or after the code block.