fix(extraction): count all file-level-tracked langs (incl .properties) as indexed (#544)
Completes #357. The no-symbol file-level class is yaml/twig/properties, but the count fix only covered yaml/twig — so a .properties-only project still printed "No files found to index" even though the files were stored. Introduce a single isFileLevelOnlyLanguage predicate (the canonical set behind the tree-sitter no-symbol branch, xml excluded since its MyBatis extractor emits a file node) and use it at both count sites and the extraction dispatch so the list can't drift. Adds .properties regression coverage for indexAll() and indexFiles(). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
839cf63dcb
commit
cdbf451440
@@ -9,6 +9,10 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixes
|
||||
|
||||
- Indexing a project that contains only config-style files (YAML, Twig, or `.properties`) no longer misleadingly reports "No files found to index" — these files are tracked at the file level and are now counted as indexed. Thanks @luojiyin1987 (#357).
|
||||
|
||||
## [0.9.7] - 2026-05-28
|
||||
|
||||
### New Features
|
||||
|
||||
@@ -3258,6 +3258,38 @@ export function multiply(a: number, b: number): number {
|
||||
|
||||
cg.close();
|
||||
});
|
||||
|
||||
it('should count file-level tracked .properties files as indexed', async () => {
|
||||
fs.writeFileSync(path.join(tempDir, 'application.properties'), 'server.port=8080\n');
|
||||
fs.writeFileSync(path.join(tempDir, 'log.properties'), 'log.level=INFO\n');
|
||||
|
||||
const cg = CodeGraph.initSync(tempDir);
|
||||
const result = await cg.indexAll();
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.filesIndexed).toBe(2);
|
||||
expect(result.filesSkipped).toBe(0);
|
||||
|
||||
cg.close();
|
||||
});
|
||||
|
||||
it('should count the full file-level tracked class (yaml/twig/properties) in indexFiles()', async () => {
|
||||
fs.writeFileSync(path.join(tempDir, 'app.yaml'), 'name: test\n');
|
||||
fs.writeFileSync(path.join(tempDir, 'view.twig'), '{{ title }}\n');
|
||||
fs.writeFileSync(path.join(tempDir, 'application.properties'), 'server.port=8080\n');
|
||||
|
||||
const cg = CodeGraph.initSync(tempDir);
|
||||
const result = await cg.indexFiles(['app.yaml', 'view.twig', 'application.properties']);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.filesIndexed).toBe(3);
|
||||
expect(result.filesSkipped).toBe(0);
|
||||
|
||||
const tracked = cg.getFiles().map((f) => `${f.path}:${f.language}`).sort();
|
||||
expect(tracked).toEqual(['app.yaml:yaml', 'application.properties:properties', 'view.twig:twig']);
|
||||
|
||||
cg.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Path Normalization', () => {
|
||||
|
||||
@@ -290,6 +290,19 @@ export function isGrammarLoaded(language: Language): boolean {
|
||||
return languageCache.has(language);
|
||||
}
|
||||
|
||||
/**
|
||||
* Languages tracked at the file-record level only: parsing emits zero symbol
|
||||
* nodes, but the file is still stored (and framework resolvers may add per-file
|
||||
* references later, e.g. Drupal routing yml, Spring `@Value` against
|
||||
* application.properties). This is the canonical set behind the no-symbol
|
||||
* branch in `tree-sitter.ts`; `xml` is intentionally excluded because its
|
||||
* MyBatis extractor emits a file node. Callers use this to count such files as
|
||||
* indexed rather than skipped, so it must stay in sync with that branch.
|
||||
*/
|
||||
export function isFileLevelOnlyLanguage(language: Language): boolean {
|
||||
return language === 'yaml' || language === 'twig' || language === 'properties';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all supported languages (those with grammar definitions).
|
||||
*/
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from '../types';
|
||||
import { QueryBuilder } from '../db/queries';
|
||||
import { extractFromSource } from './tree-sitter';
|
||||
import { detectLanguage, isSourceFile, isLanguageSupported, initGrammars, loadGrammarsForLanguages } from './grammars';
|
||||
import { detectLanguage, isSourceFile, isLanguageSupported, isFileLevelOnlyLanguage, initGrammars, loadGrammarsForLanguages } from './grammars';
|
||||
import { logDebug, logWarn } from '../errors';
|
||||
import { validatePathWithinRoot, normalizePath } from '../utils';
|
||||
import ignore, { Ignore } from 'ignore';
|
||||
@@ -942,11 +942,11 @@ export class ExtractionOrchestrator {
|
||||
} else if (result.errors.some((e) => e.severity === 'error')) {
|
||||
filesErrored++;
|
||||
} else {
|
||||
// Files with no symbols but no errors (e.g. yaml, twig) are tracked
|
||||
// at the file level — count them as indexed so the CLI doesn't
|
||||
// misleadingly report "No files found to index".
|
||||
// Files with no symbols but no errors (yaml, twig, properties) are
|
||||
// tracked at the file level — count them as indexed so the CLI
|
||||
// doesn't misleadingly report "No files found to index".
|
||||
const lang = detectLanguage(filePath, content);
|
||||
if (lang === 'yaml' || lang === 'twig') {
|
||||
if (isFileLevelOnlyLanguage(lang)) {
|
||||
filesIndexed++;
|
||||
} else {
|
||||
filesSkipped++;
|
||||
@@ -1117,7 +1117,7 @@ export class ExtractionOrchestrator {
|
||||
filesErrored++;
|
||||
} else {
|
||||
const tracked = this.queries.getFileByPath(filePath);
|
||||
if (tracked && (tracked.language === 'yaml' || tracked.language === 'twig')) {
|
||||
if (tracked && isFileLevelOnlyLanguage(tracked.language)) {
|
||||
filesIndexed++;
|
||||
} else {
|
||||
filesSkipped++;
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
ExtractionError,
|
||||
UnresolvedReference,
|
||||
} from '../types';
|
||||
import { getParser, detectLanguage, isLanguageSupported } from './grammars';
|
||||
import { getParser, detectLanguage, isLanguageSupported, isFileLevelOnlyLanguage } from './grammars';
|
||||
import { generateNodeId, getNodeText, getChildByField, getPrecedingDocstring } from './tree-sitter-helpers';
|
||||
import type { LanguageExtractor, ExtractorContext } from './tree-sitter-types';
|
||||
import { EXTRACTORS } from './languages';
|
||||
@@ -3072,7 +3072,7 @@ export function extractFromSource(
|
||||
// file node so the watcher tracks it without emitting symbols.
|
||||
const extractor = new MyBatisExtractor(filePath, source);
|
||||
result = extractor.extract();
|
||||
} else if (detectedLanguage === 'yaml' || detectedLanguage === 'twig' || detectedLanguage === 'properties') {
|
||||
} else if (isFileLevelOnlyLanguage(detectedLanguage)) {
|
||||
// No symbol extraction at this stage — files are tracked at the file-record
|
||||
// level only. Framework extractors (Drupal routing yml, Spring `@Value`
|
||||
// resolution against application.yml/application.properties) run later and
|
||||
|
||||
Reference in New Issue
Block a user