فهرست منبع

Readme updated and detect test files in kotlin and swift

Colby McHenry 1 ماه پیش
والد
کامیت
d2664e87b0
3فایلهای تغییر یافته به همراه90 افزوده شده و 30 حذف شده
  1. 1 1
      README.md
  2. 53 0
      __tests__/is-test-file.test.ts
  3. 36 29
      src/search/query-utils.ts

+ 1 - 1
README.md

@@ -8,7 +8,7 @@
 
 [![npm version](https://img.shields.io/npm/v/@colbymchenry/codegraph.svg)](https://www.npmjs.com/package/@colbymchenry/codegraph)
 [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
-[![Node.js](https://img.shields.io/badge/Node.js-18+-green.svg)](https://nodejs.org/)
+[![Node.js](https://img.shields.io/badge/Node.js-20--24-green.svg)](https://nodejs.org/)
 
 [![Windows](https://img.shields.io/badge/Windows-supported-blue.svg)](#)
 [![macOS](https://img.shields.io/badge/macOS-supported-blue.svg)](#)

+ 53 - 0
__tests__/is-test-file.test.ts

@@ -0,0 +1,53 @@
+/**
+ * isTestFile heuristic — test-file detection used to deprioritize test code in
+ * search/explore ranking.
+ *
+ * Regression coverage for the cold-query fix: the heuristic previously only
+ * knew Java/JS/Python conventions, so Kotlin (`*Test.kt`, `jvmTest/`), Swift
+ * (`*Tests.swift`), and camelCase test source-set dirs slipped through — which
+ * let OkHttp's tests flood `codegraph_explore` results on a plain-language
+ * query. The false-positive guards matter just as much: `latest.kt` /
+ * `manifest.kt` / a `RealCall.kt` production file must NOT be flagged.
+ */
+import { describe, it, expect } from 'vitest';
+import { isTestFile } from '../src/search/query-utils';
+
+describe('isTestFile', () => {
+  it('flags Kotlin test files and source sets', () => {
+    expect(isTestFile('okhttp/src/jvmTest/kotlin/okhttp3/CallTest.kt')).toBe(true);
+    expect(isTestFile('okhttp/src/commonTest/kotlin/okhttp3/CompressionInterceptorTest.kt')).toBe(true);
+    expect(isTestFile('app/src/androidTest/java/com/example/FooTest.kt')).toBe(true);
+    expect(isTestFile('module/src/integrationTest/kotlin/BarSpec.kt')).toBe(true);
+  });
+
+  it('flags Swift test files', () => {
+    expect(isTestFile('Tests/SessionTests.swift')).toBe(true);
+    expect(isTestFile('Sources/FooTest.swift')).toBe(true);
+  });
+
+  it('still flags the previously-supported conventions', () => {
+    expect(isTestFile('foo/test_bar.py')).toBe(true);
+    expect(isTestFile('pkg/bar_test.go')).toBe(true);
+    expect(isTestFile('src/foo.test.ts')).toBe(true);
+    expect(isTestFile('src/foo.spec.ts')).toBe(true);
+    expect(isTestFile('com/example/FooTest.java')).toBe(true);
+    expect(isTestFile('com/example/FooTestCase.java')).toBe(true);
+    expect(isTestFile('project/__tests__/foo.ts')).toBe(true);
+    expect(isTestFile('project/tests/foo.rb')).toBe(true);
+  });
+
+  it('does NOT flag production files that merely contain "test" lowercase', () => {
+    // The fix is capital-led so camelCase boundaries distinguish these.
+    expect(isTestFile('src/latest/loader.kt')).toBe(false);
+    expect(isTestFile('lib/manifest.kt')).toBe(false);
+    expect(isTestFile('okhttp/src/jvmMain/kotlin/okhttp3/internal/connection/RealCall.kt')).toBe(false);
+    expect(isTestFile('src/contestEntry.ts')).toBe(false);
+    expect(isTestFile('pkg/greatest.go')).toBe(false);
+  });
+
+  it('does NOT flag ordinary production source', () => {
+    expect(isTestFile('src/flask/app.py')).toBe(false);
+    expect(isTestFile('src/vs/workbench/api/common/extensionHostMain.ts')).toBe(false);
+    expect(isTestFile('okhttp/src/commonJvmAndroid/kotlin/okhttp3/OkHttpClient.kt')).toBe(false);
+  });
+});

+ 36 - 29
src/search/query-utils.ts

@@ -207,36 +207,43 @@ export function scorePathRelevance(filePath: string, query: string): number {
  */
 export function isTestFile(filePath: string): boolean {
   const lower = filePath.toLowerCase();
-  const fileName = path.basename(lower);
-
-  // Common test file patterns
-  return (
-    fileName.startsWith('test_') ||
-    fileName.startsWith('test.') ||
-    fileName.endsWith('.test.ts') ||
-    fileName.endsWith('.test.js') ||
-    fileName.endsWith('.test.tsx') ||
-    fileName.endsWith('.test.jsx') ||
-    fileName.endsWith('.spec.ts') ||
-    fileName.endsWith('.spec.js') ||
-    fileName.endsWith('_test.go') ||
-    fileName.endsWith('_test.py') ||
-    fileName.endsWith('_test.rs') ||
-    fileName.endsWith('Tests.java') ||
-    fileName.endsWith('Test.java') ||
-    fileName.endsWith('Tester.java') ||
-    fileName.endsWith('TestCase.java') ||
-    lower.includes('/tests/') ||
-    lower.includes('/test/') ||
-    lower.includes('/__tests__/') ||
-    lower.includes('/spec/') ||
-    lower.includes('/testlib/') ||
+  const fileName = path.basename(filePath);   // original case — needed for camelCase boundaries
+  const lowerName = fileName.toLowerCase();
+
+  // --- Filename patterns ---
+  if (
+    lowerName.startsWith('test_') ||                              // python: test_foo.py
+    lowerName.startsWith('test.') ||
+    // separator-delimited: foo_test.go, foo.test.ts, foo-spec.rb, bar_spec.py
+    /[._-](test|tests|spec|specs)\.[a-z0-9]+$/.test(lowerName) ||
+    // CamelCase suffix (Java/Kotlin/Swift/C#/Scala): FooTest.kt, BarTests.swift,
+    // BazSpec.scala, QuxTestCase.java. Capital-led so "latest.kt"/"manifest.kt"
+    // (lowercase "test") are NOT matched.
+    /(?:Test|Tests|TestCase|Tester|Spec|Specs)\.[A-Za-z0-9]+$/.test(fileName)
+  ) {
+    return true;
+  }
+
+  // --- Directory patterns ---
+  if (
+    lower.includes('/tests/') || lower.includes('/test/') ||
+    lower.includes('/__tests__/') || lower.includes('/spec/') ||
+    lower.includes('/specs/') || lower.includes('/testlib/') ||
     lower.includes('/testing/') ||
-    // Non-production directories: examples, samples, benchmarks, fixtures, demos.
-    // Check both mid-path (/integration/) and start-of-path (integration/) since
-    // file paths may be stored as relative paths without a leading slash.
-    matchesNonProductionDir(lower)
-  );
+    lower.startsWith('test/') || lower.startsWith('tests/') ||
+    lower.startsWith('spec/') || lower.startsWith('specs/') ||
+    // CamelCase test source-set dirs (Kotlin Multiplatform / Gradle / Xcode):
+    // jvmTest/, commonTest/, androidTest/, iosTest/, integrationTest/. Capital-led
+    // so "latest/" / "manifest/" are not matched.
+    /(?:^|\/)[A-Za-z0-9]*(?:Test|Tests|Spec)\//.test(filePath)
+  ) {
+    return true;
+  }
+
+  // Non-production directories: examples, samples, benchmarks, fixtures, demos.
+  // Check both mid-path (/integration/) and start-of-path (integration/) since
+  // file paths may be stored as relative paths without a leading slash.
+  return matchesNonProductionDir(lower);
 }
 
 /**