@opensip-tools/checks-cpp 1.0.4

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,4 @@
1
+
2
+ > @opensip-tools/checks-cpp@1.0.4 typecheck /Users/sb/Documents/Code/opensip-ai/opensip-tools/packages/fitness/checks-cpp
3
+ > tsc --noEmit
4
+
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 opensip-ai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@opensip-tools/checks-cpp",
3
+ "version": "1.0.4",
4
+ "license": "MIT",
5
+ "description": "C/C++ fitness checks (clang-tidy backed)",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/opensip-ai/opensip-tools.git",
9
+ "directory": "packages/fitness/checks-cpp"
10
+ },
11
+ "homepage": "https://github.com/opensip-ai/opensip-tools",
12
+ "bugs": {
13
+ "url": "https://github.com/opensip-ai/opensip-tools/issues"
14
+ },
15
+ "type": "module",
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": "./dist/index.js"
20
+ },
21
+ "dependencies": {
22
+ "@opensip-tools/fitness": "1.0.4"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^22.0.0",
26
+ "vitest": "^2.1.0"
27
+ },
28
+ "scripts": {
29
+ "build": "tsc",
30
+ "test": "vitest run --passWithNoTests",
31
+ "typecheck": "tsc --noEmit",
32
+ "clean": "rm -rf dist"
33
+ }
34
+ }
@@ -0,0 +1,54 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { parseClangTidyOutput } from '../checks/clang-tidy-passthrough.js'
4
+
5
+ describe('parseClangTidyOutput', () => {
6
+ it('parses a single warning with a lint name', () => {
7
+ const out = `/abs/path/foo.cpp:42:10: warning: do not use 'goto' [hicpp-avoid-goto]`
8
+ const violations = parseClangTidyOutput(out, '', 0, ['/abs/path/foo.cpp'], '/abs')
9
+ expect(violations).toHaveLength(1)
10
+ expect(violations[0]?.severity).toBe('warning')
11
+ expect(violations[0]?.line).toBe(42)
12
+ expect(violations[0]?.message).toContain('hicpp-avoid-goto')
13
+ })
14
+
15
+ it('parses errors with severity error', () => {
16
+ const out = `/x/y.cpp:5:1: error: expected ';' [clang-diagnostic-error]`
17
+ const violations = parseClangTidyOutput(out, '', 1, ['/x/y.cpp'], '/x')
18
+ expect(violations).toHaveLength(1)
19
+ expect(violations[0]?.severity).toBe('error')
20
+ })
21
+
22
+ it('skips note: lines (notes are continuations of prior diagnostics)', () => {
23
+ const out = [
24
+ '/x/y.cpp:5:1: warning: prefer enum class [modernize-use-enum-class]',
25
+ '/x/y.cpp:5:1: note: change here',
26
+ ].join('\n')
27
+ const violations = parseClangTidyOutput(out, '', 0, [], '/x')
28
+ expect(violations).toHaveLength(1)
29
+ })
30
+
31
+ it('handles diagnostics without a lint name', () => {
32
+ const out = `/a/b.cpp:1:1: warning: bare warning`
33
+ const violations = parseClangTidyOutput(out, '', 0, [], '/a')
34
+ expect(violations).toHaveLength(1)
35
+ expect(violations[0]?.message).toBe('bare warning')
36
+ })
37
+
38
+ it('returns empty array on empty stdout', () => {
39
+ expect(parseClangTidyOutput('', '', 0, [], '/x')).toHaveLength(0)
40
+ })
41
+
42
+ it('returns multiple violations for multi-line output', () => {
43
+ const out = [
44
+ '/x/y.cpp:1:1: warning: a [check-a]',
45
+ '/x/y.cpp:5:1: warning: b [check-b]',
46
+ '/x/y.cpp:9:1: error: c [check-c]',
47
+ ].join('\n')
48
+ const violations = parseClangTidyOutput(out, '', 0, [], '/x')
49
+ expect(violations).toHaveLength(3)
50
+ expect(violations[0]?.line).toBe(1)
51
+ expect(violations[1]?.line).toBe(5)
52
+ expect(violations[2]?.line).toBe(9)
53
+ })
54
+ })
@@ -0,0 +1,71 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { parseClangTidyOutput } from '../checks/clang-tidy-passthrough.js';
4
+
5
+ describe('parseClangTidyOutput', () => {
6
+ it('parses a warning line with a [check-name] tag', () => {
7
+ const out = parseClangTidyOutput(
8
+ 'src/foo.cpp:42:10: warning: unused variable [misc-unused]',
9
+ '',
10
+ 0,
11
+ [],
12
+ '',
13
+ );
14
+ expect(out).toHaveLength(1);
15
+ expect(out[0]?.severity).toBe('warning');
16
+ expect(out[0]?.line).toBe(42);
17
+ expect(out[0]?.message).toContain('[misc-unused]');
18
+ expect(out[0]?.message).toContain('unused variable');
19
+ });
20
+
21
+ it('parses an error line', () => {
22
+ const out = parseClangTidyOutput(
23
+ 'src/x.cpp:5:1: error: something exploded [bugprone-foo]',
24
+ '',
25
+ 0,
26
+ [],
27
+ '',
28
+ );
29
+ expect(out[0]?.severity).toBe('error');
30
+ });
31
+
32
+ it('parses a line without a [check-name] tag', () => {
33
+ const out = parseClangTidyOutput('src/y.cpp:1:1: warning: unparsed', '', 0, [], '');
34
+ expect(out).toHaveLength(1);
35
+ expect(out[0]?.message).toBe('unparsed');
36
+ });
37
+
38
+ it('skips note: lines', () => {
39
+ const out = parseClangTidyOutput(
40
+ 'src/a.cpp:1:1: warning: w [misc]\nsrc/a.cpp:1:1: note: more info',
41
+ '',
42
+ 0,
43
+ [],
44
+ '',
45
+ );
46
+ expect(out).toHaveLength(1);
47
+ });
48
+
49
+ it('skips lines that do not match the expected format', () => {
50
+ const out = parseClangTidyOutput(
51
+ 'random output\n2 warnings generated.\n',
52
+ '',
53
+ 0,
54
+ [],
55
+ '',
56
+ );
57
+ expect(out).toEqual([]);
58
+ });
59
+
60
+ it('handles empty output', () => {
61
+ expect(parseClangTidyOutput('', '', 0, [], '')).toEqual([]);
62
+ });
63
+
64
+ it('parses multiple diagnostics', () => {
65
+ const stdout = 'a.cpp:1:1: warning: w1 [m1]\nb.cpp:2:2: error: e1 [m2]';
66
+ const out = parseClangTidyOutput(stdout, '', 0, [], '');
67
+ expect(out).toHaveLength(2);
68
+ expect(out[0]?.severity).toBe('warning');
69
+ expect(out[1]?.severity).toBe('error');
70
+ });
71
+ });
@@ -0,0 +1,63 @@
1
+ /**
2
+ * @fileoverview clang-tidy passthrough check.
3
+ *
4
+ * Runs `clang-tidy` against the matched files and surfaces its
5
+ * diagnostics as opensip-tools violations. The user's `.clang-tidy`
6
+ * config (if present) controls which lints fire — we don't override
7
+ * it. Use `--checks=...` in the args if you want a fixed lint set.
8
+ */
9
+ import { defineCheck, type CheckViolation } from '@opensip-tools/fitness'
10
+
11
+ // eslint-disable-next-line sonarjs/slow-regex -- input is one bounded line of clang-tidy output; no real ReDoS exposure
12
+ const CLANG_TIDY_LINE = /^(.+?):(\d+):(\d+):\s+(warning|error|note):\s+(.+?)(?:\s+\[([\w\-,.]+)\])?$/
13
+
14
+ /**
15
+ * Pure parser for clang-tidy stdout. Accepts the diagnostic format:
16
+ * path/to/file.cpp:LINE:COL: warning: <message> [check-name]
17
+ * Returns one CheckViolation per warning/error line. `note:` lines
18
+ * are attached to the prior diagnostic when possible (kept simple
19
+ * for MVP — current implementation skips them).
20
+ */
21
+ export function parseClangTidyOutput(
22
+ stdout: string,
23
+ _stderr: string,
24
+ _exitCode: number,
25
+ _files: readonly string[],
26
+ _cwd: string,
27
+ ): CheckViolation[] {
28
+ const violations: CheckViolation[] = []
29
+ const lines = stdout.split('\n')
30
+ for (const line of lines) {
31
+ const match = CLANG_TIDY_LINE.exec(line)
32
+ if (!match) continue
33
+ const lineStr = match[2]
34
+ const severity = match[4]
35
+ const message = match[5]
36
+ const lintName = match[6]
37
+ if (severity === 'note') continue
38
+ violations.push({
39
+ message: lintName ? `[${lintName}] ${message}` : message ?? 'clang-tidy diagnostic',
40
+ severity: severity === 'error' ? 'error' : 'warning',
41
+ line: lineStr ? Number.parseInt(lineStr, 10) : 1,
42
+ suggestion: 'See clang-tidy docs for the named lint',
43
+ })
44
+ }
45
+ return violations
46
+ }
47
+
48
+ export const clangTidyPassthrough = defineCheck({
49
+ id: 'e1f2a3b4-9876-4321-eeee-500000000001',
50
+ slug: 'cpp-clang-tidy',
51
+ description: 'Run clang-tidy and surface its diagnostics as opensip-tools violations',
52
+ scope: { languages: ['cpp'], concerns: [] },
53
+ tags: ['quality', 'cpp'],
54
+ command: {
55
+ bin: 'clang-tidy',
56
+ args: (files) => [...files, '--quiet'],
57
+ parseOutput: parseClangTidyOutput,
58
+ // clang-tidy returns 1 when warnings are emitted with -warnings-as-errors,
59
+ // but with --quiet and default config, exit 0 means clean run.
60
+ // Diagnostics are present on stdout regardless.
61
+ expectedExitCodes: [0, 1],
62
+ },
63
+ })
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { clangTidyPassthrough } from './checks/clang-tidy-passthrough.js'
2
+
3
+ export const checks = [clangTidyPassthrough] as const
4
+ export { clangTidyPassthrough, parseClangTidyOutput } from './checks/clang-tidy-passthrough.js'
5
+
6
+ export const metadata = {
7
+ name: '@opensip-tools/checks-cpp',
8
+ version: '0.6.1',
9
+ description: 'C/C++ fitness checks (clang-tidy backed)',
10
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src"]
8
+ }
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'vitest/config';
2
+ export default defineConfig({
3
+ test: {
4
+ include: ['src/**/*.test.ts'],
5
+ coverage: {
6
+ include: ['src/**'],
7
+ exclude: ['src/**/*.test.ts', 'src/**/__tests__/**', 'src/index.ts'],
8
+ },
9
+ },
10
+ });