@opensip-tools/checks-java 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-java@1.0.4 typecheck /Users/sb/Documents/Code/opensip-ai/opensip-tools/packages/fitness/checks-java
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,35 @@
1
+ {
2
+ "name": "@opensip-tools/checks-java",
3
+ "version": "1.0.4",
4
+ "license": "MIT",
5
+ "description": "Java fitness checks for opensip-tools",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/opensip-ai/opensip-tools.git",
9
+ "directory": "packages/fitness/checks-java"
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
+ "@opensip-tools/lang-java": "1.0.4"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^22.0.0",
27
+ "vitest": "^2.1.0"
28
+ },
29
+ "scripts": {
30
+ "build": "tsc",
31
+ "test": "vitest run --passWithNoTests",
32
+ "typecheck": "tsc --noEmit",
33
+ "clean": "rm -rf dist"
34
+ }
35
+ }
@@ -0,0 +1,33 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { analyzePrintStackTrace } from '../checks/no-printstacktrace.js';
4
+
5
+ describe('analyzePrintStackTrace', () => {
6
+ it('flags e.printStackTrace()', () => {
7
+ const out = analyzePrintStackTrace('try { foo(); } catch (Exception e) { e.printStackTrace(); }');
8
+ expect(out).toHaveLength(1);
9
+ expect(out[0]?.severity).toBe('warning');
10
+ });
11
+
12
+ it('flags printStackTrace() with whitespace', () => {
13
+ expect(analyzePrintStackTrace('e.printStackTrace ( )')).toHaveLength(1);
14
+ });
15
+
16
+ it('reports the correct line', () => {
17
+ const out = analyzePrintStackTrace('class X {\n void f(Exception e) {\n e.printStackTrace();\n }\n}');
18
+ expect(out[0]?.line).toBe(3);
19
+ });
20
+
21
+ it('does not flag printStackTrace with arguments', () => {
22
+ expect(analyzePrintStackTrace('e.printStackTrace(System.err)')).toHaveLength(0);
23
+ });
24
+
25
+ it('flags multiple occurrences in one file', () => {
26
+ const src = 'a.printStackTrace();\nb.printStackTrace();';
27
+ expect(analyzePrintStackTrace(src)).toHaveLength(2);
28
+ });
29
+
30
+ it('returns an empty list when no .printStackTrace() is present', () => {
31
+ expect(analyzePrintStackTrace('class X {}')).toEqual([]);
32
+ });
33
+ });
@@ -0,0 +1,59 @@
1
+ import { stripComments, stripStrings } from '@opensip-tools/lang-java/strip'
2
+ import { describe, expect, it } from 'vitest'
3
+
4
+ import { analyzePrintStackTrace } from '../checks/no-printstacktrace.js'
5
+
6
+ describe('analyzePrintStackTrace', () => {
7
+ it('flags e.printStackTrace()', () => {
8
+ const violations = analyzePrintStackTrace('e.printStackTrace();')
9
+ expect(violations.length).toBe(1)
10
+ expect(violations[0]?.line).toBe(1)
11
+ expect(violations[0]?.message).toContain('logging framework')
12
+ })
13
+
14
+ it('does not flag the same literal text inside a string after stripping', () => {
15
+ // The content filter (strip-strings-and-comments) is applied by the
16
+ // framework before calling analyze. Simulate that here.
17
+ const src = 'String s = "e.printStackTrace()";'
18
+ const filtered = stripStrings(src)
19
+ const violations = analyzePrintStackTrace(filtered)
20
+ expect(violations.length).toBe(0)
21
+ })
22
+
23
+ it('does not flag occurrences inside comments after stripping', () => {
24
+ const src = '// see e.printStackTrace() for example\nint x = 1;'
25
+ const filtered = stripComments(src)
26
+ const violations = analyzePrintStackTrace(filtered)
27
+ expect(violations.length).toBe(0)
28
+ })
29
+
30
+ it('reports correct line numbers for multiple matches', () => {
31
+ const src = [
32
+ 'class A {',
33
+ ' void m(Throwable e) {',
34
+ ' e.printStackTrace();',
35
+ ' int x = 1;',
36
+ ' e.printStackTrace();',
37
+ ' }',
38
+ '}',
39
+ ].join('\n')
40
+ const violations = analyzePrintStackTrace(src)
41
+ expect(violations.length).toBe(2)
42
+ expect(violations[0]?.line).toBe(3)
43
+ expect(violations[1]?.line).toBe(5)
44
+ })
45
+
46
+ it('flags calls with whitespace inside the parens', () => {
47
+ const violations = analyzePrintStackTrace('e.printStackTrace( );')
48
+ expect(violations.length).toBe(1)
49
+ })
50
+
51
+ it('does not flag printStackTrace with arguments (different signature)', () => {
52
+ // printStackTrace(PrintStream) is a legitimate call — only the
53
+ // no-arg variant goes to System.err implicitly.
54
+ const violations = analyzePrintStackTrace(
55
+ 'e.printStackTrace(System.out);',
56
+ )
57
+ expect(violations.length).toBe(0)
58
+ })
59
+ })
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @fileoverview Flag uses of `Throwable.printStackTrace()`.
3
+ *
4
+ * `e.printStackTrace()` writes to stderr, bypassing the application's
5
+ * logging framework. Stack traces emitted that way miss correlation
6
+ * IDs, log levels, and structured fields, and they typically don't
7
+ * land in centralized log aggregation. Always log via the configured
8
+ * logger (SLF4J/Log4j/etc.) and pass the throwable as the cause.
9
+ *
10
+ * The check uses the `strip-strings-and-comments` content filter so
11
+ * the literal token "printStackTrace()" inside a string literal or
12
+ * comment isn't reported.
13
+ */
14
+ import { defineCheck, type CheckViolation } from '@opensip-tools/fitness'
15
+
16
+ const PRINT_STACK_TRACE_PATTERN = /\.printStackTrace\s*\(\s*\)/g
17
+
18
+ /**
19
+ * Pure analysis function. Exported so unit tests can exercise the
20
+ * detection logic without standing up the full Check framework
21
+ * (defineCheck wraps `analyze` into an `execute` closure that
22
+ * requires an ExecutionContext to invoke).
23
+ */
24
+ export function analyzePrintStackTrace(content: string): CheckViolation[] {
25
+ const violations: CheckViolation[] = []
26
+ const lines = content.split('\n')
27
+ for (const [i, line_] of lines.entries()) {
28
+ const line = line_
29
+ PRINT_STACK_TRACE_PATTERN.lastIndex = 0
30
+ while (PRINT_STACK_TRACE_PATTERN.exec(line) !== null) {
31
+ violations.push({
32
+ message:
33
+ 'e.printStackTrace() bypasses the logging framework — use a logger instead',
34
+ severity: 'warning',
35
+ line: i + 1,
36
+ suggestion:
37
+ 'Replace with logger.error("...", e) so the trace lands in centralized logs',
38
+ })
39
+ }
40
+ }
41
+ return violations
42
+ }
43
+
44
+ export const noPrintStackTrace = defineCheck({
45
+ id: 'c1d2e3f4-9876-4321-cccc-300000000001',
46
+ slug: 'java-no-print-stack-trace',
47
+ description:
48
+ 'e.printStackTrace() bypasses the logging framework — use a logger instead',
49
+ scope: { languages: ['java'], concerns: [] },
50
+ tags: ['quality', 'observability', 'java'],
51
+ // Strip strings AND comments so the literal token inside a docstring
52
+ // or example comment isn't false-flagged.
53
+ contentFilter: 'strip-strings-and-comments',
54
+ analyze: (content) => analyzePrintStackTrace(content),
55
+ })
package/src/index.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { noPrintStackTrace } from './checks/no-printstacktrace.js'
2
+
3
+ export const checks = [noPrintStackTrace] as const
4
+
5
+
6
+ export const metadata = {
7
+ name: '@opensip-tools/checks-java',
8
+ version: '0.6.1',
9
+ description: 'Java fitness checks',
10
+ }
11
+
12
+ export {noPrintStackTrace} from './checks/no-printstacktrace.js'
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
+ });