From 5e94a7917356226ff057f4c548cc07e7226ef93c Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Sun, 25 Jul 2021 15:38:55 +0000 Subject: [PATCH] feat(lint): add clang-tidy support --- package-lock.json | 7 +- package.json | 3 +- src/lint/lint.service.ts | 101 +++++++++++++++++++++- src/submissions/submissions.controller.ts | 1 + 4 files changed, 106 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 77772c0..fa815cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,8 @@ "rxjs": "^7.2.0", "typedjson": "^1.7.0", "typeorm": "^0.2.34", - "uuid": "^3.4.0" + "uuid": "^3.4.0", + "yaml": "^1.10.2" }, "devDependencies": { "@nestjs/cli": "8.0.2", @@ -12713,7 +12714,6 @@ "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, "engines": { "node": ">= 6" } @@ -22712,8 +22712,7 @@ "yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" }, "yargonaut": { "version": "1.1.4", diff --git a/package.json b/package.json index 686f170..3cb832b 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,8 @@ "rxjs": "^7.2.0", "typedjson": "^1.7.0", "typeorm": "^0.2.34", - "uuid": "^3.4.0" + "uuid": "^3.4.0", + "yaml": "^1.10.2" }, "devDependencies": { "@nestjs/cli": "8.0.2", diff --git a/src/lint/lint.service.ts b/src/lint/lint.service.ts index 3507e29..36d13c3 100644 --- a/src/lint/lint.service.ts +++ b/src/lint/lint.service.ts @@ -1,7 +1,12 @@ +// `yaml` is not type safe 🤷‍♂️ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { Injectable, InternalServerErrorException } from '@nestjs/common'; import child_process from 'child_process'; import fs from 'fs'; import * as uuid from 'uuid'; +import YAML from 'yaml'; import { ConvertToGolangCILintOutput } from './linters/golangcilint'; import { ConvertToPylintOutput } from './linters/pylint'; @@ -26,7 +31,24 @@ export class LintService { output = output.substring(0, output.length - 1); const pylintOutput = ConvertToPylintOutput.toPylintOutput(output); if (pylintOutput) { - // Remove one point to the score per violation + /* + Example output: + [ + { + "type": "convention", + "module": "fib", + "obj": "", + "line": 1, + "column": 0, + "path": "fib.py", + "symbol": "missing-module-docstring", + "message": "Missing module docstring", + "message-id": "C0114" + }, + ... + + Remove one point to the score per violation + */ return 100 - pylintOutput.length; } } catch (e) { @@ -63,6 +85,31 @@ export class LintService { const lintOuput = ConvertToGolangCILintOutput.toGolangCILintOutput(output); if (lintOuput) { + /* + Example output: + + { + "Issues": [ + { + "FromLinter": "errcheck", + "Text": "Error return value of `http.ListenAndServe` is not checked", + "Severity": "", + "SourceLines": [ + "\thttp.ListenAndServe(\":9567\", nil)" + ], + "Replacement": null, + "Pos": { + "Filename": "http.go", + "Offset": 417, + "Line": 27, + "Column": 21 + }, + "ExpectNoLint": false, + "ExpectedNoLintLinter": "" + } + ], + ... + */ if (lintOuput.issues) { // Remove one point to the score per violation return 100 - lintOuput.issues.length; @@ -76,4 +123,56 @@ export class LintService { throw new InternalServerErrorException(); } + + lintCpp(code: string): number { + // clang-tidy doesn't support stdin + const path = `/tmp/codebench_${uuid.v4()}.cpp`; + const outputPath = `${path}.yaml`; + try { + fs.writeFileSync(path, code); + } catch (e) { + throw new InternalServerErrorException(e); + } + + const result = child_process.spawnSync('clang-tidy', [ + `-export-fixes=${outputPath}`, + path, + '--', + '-Wall', + '-std=c++11', + '-x', + 'c++', + ]); + + if (result.error) { + throw new InternalServerErrorException(result.error); + } + + try { + if (fs.existsSync(outputPath)) { + const file = fs.readFileSync(`${path}.yaml`, 'utf8'); + const fixes: any = YAML.parse(file); + if (fixes) { + /* + Example file: + --- + MainSourceFile: /root/fib.cpp + Diagnostics: + - DiagnosticName: clang-diagnostic-unused-variable + Message: 'unused variable ''d''' + FileOffset: 142 + FilePath: /root/fib.cpp + Replacements: + ... + */ + if (fixes.Diagnostics) { + return 100 - fixes.Diagnostics.length; + } + } + } + return 100; + } catch (e) { + throw new InternalServerErrorException(e); + } + } } diff --git a/src/submissions/submissions.controller.ts b/src/submissions/submissions.controller.ts index cb2b497..e4e1423 100644 --- a/src/submissions/submissions.controller.ts +++ b/src/submissions/submissions.controller.ts @@ -45,6 +45,7 @@ export class SubmissionsController { break; case 'cpp': qualityScore = this.qualityService.run(createSubmissionDTO.code, 'cpp'); + lintScore.score = this.lintService.lintCpp(createSubmissionDTO.code); break; case 'go': qualityScore = this.qualityService.run(createSubmissionDTO.code, 'go');