mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-16 20:53:57 +08:00
* fix(pascal): Delphi .inc include files are indexed as Pascal, not PHP (#2279) `.inc` maps to PHP (Drupal), so a Delphi/Free Pascal include — a `{$IFDEF}`/`{$DEFINE}` block or a declaration fragment pulled in with `{$I defs.inc}` — was parsed as PHP (untagged text = inline HTML) and contributed nothing. detectLanguage now decides `.inc` by content: a PHP open tag (`<?php`, `<?=`) anywhere keeps it PHP; otherwise a Pascal-only line shape (a compiler directive, a `;`-closed routine header, `unit`/`uses`, a const/var/type section, a begin/end; pair, Lazarus `{%MainUnit}`) makes it Pascal; anything else keeps the PHP routing so C / asm / POV-Ray / SourcePawn / Makefile includes are not handed to the Pascal grammar. A leading BOM is skipped. An explicit codegraph.json `.inc` mapping wins. The rule is per file, so a full index and a sync always agree. The grammar preload set adds Pascal whenever an `.inc` is present (an include-only tree otherwise had no Pascal parser), and the kernel-parity script re-detects `.inc` like the indexer does. Checked on 875 real `.inc` files: 0 of 436 non-Pascal ones (Drupal 7, php-src, LLVM, FreeBSD mk, POV-Ray, SourceMod, edk2) flip; 433 of 439 Pascal ones (FPC, Lazarus, Castle, mORMot2, JCL) are detected — the rest are statement-only or bare-literal fragments with nothing to extract. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(changelog): Pascal include files (#2279) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -165,6 +165,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
- Claude Code now shows the model the full text of `codegraph_status` and of explore's stale-index refusal. Both results also carried `structuredContent`, and Claude Code shows a result's `structuredContent` in place of its text, so the model saw only a freshness JSON object: no refusal message, no retry guidance and no status report. Both results are now text only. Thanks @bompus. (#2088)
|
||||
- When an agent asks CodeGraph about a separate git repository nested inside an indexed project, one the project's index leaves out (for example because the parent's `.gitignore` excludes it), it now gets the usual "isn't indexed" guidance instead of answers from the parent project's code. Nested repositories the parent does index, such as submodules, work as before. Thanks @wstczyw for the report. (#2110)
|
||||
- Editing a file that defines the same name more than once, like two classes that each have an `execute` method or a method's overloads, no longer moves every caller from other files onto one of them during `codegraph sync`. A sync interrupted partway through a file also no longer loses those callers until a full re-index. Thanks @ijbranch for the report. (#2276)
|
||||
- Delphi and Free Pascal include files (`.inc`) are now indexed as Pascal. Before, they were read as PHP and came up empty. A `.inc` file with a `<?php` tag is still PHP, and a `codegraph.json` mapping for `.inc` still wins. Thanks @ijbranch for the report. (#2279)
|
||||
|
||||
## [1.6.1] - 2026-09-29
|
||||
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* `.inc` is both PHP's include extension and Pascal/Delphi's (#2279).
|
||||
*
|
||||
* The extension map routes `.inc` to PHP (Drupal), so a Delphi include — a
|
||||
* `{$IFDEF}`/`{$DEFINE}` block or a declaration fragment pulled in with
|
||||
* `{$I defs.inc}` — was parsed as PHP and contributed nothing. Detection now
|
||||
* reads the file: a PHP open tag keeps it PHP, Pascal-shaped content makes it
|
||||
* Pascal, and anything else keeps the old PHP routing so C / assembly /
|
||||
* template `.inc` files are not handed to the Pascal grammar. An explicit
|
||||
* `codegraph.json` mapping for `.inc` still wins.
|
||||
*/
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import * as os from 'node:os';
|
||||
import { CodeGraph } from '../src';
|
||||
import { detectLanguage } from '../src/extraction/grammars';
|
||||
import { preloadLanguagesForFiles } from '../src/extraction';
|
||||
import { clearProjectConfigCache } from '../src/project-config';
|
||||
|
||||
const DIRECTIVES = [
|
||||
'{ Compiler version switches }',
|
||||
'{$IFDEF VER150}',
|
||||
' {$DEFINE DELPHI7}',
|
||||
'{$ENDIF}',
|
||||
'{$IFNDEF FPC}',
|
||||
' {$WARN SYMBOL_PLATFORM OFF}',
|
||||
'{$ELSE}',
|
||||
' {$MODE DELPHI}{$H+}',
|
||||
'{$ENDIF}',
|
||||
'',
|
||||
].join('\n');
|
||||
|
||||
const ROUTINES = [
|
||||
'procedure Foo;',
|
||||
'begin',
|
||||
" WriteLn('foo');",
|
||||
'end;',
|
||||
'',
|
||||
'function Bar(A: Integer): Integer;',
|
||||
'begin',
|
||||
' Result := A + 1;',
|
||||
'end;',
|
||||
'',
|
||||
].join('\n');
|
||||
|
||||
describe('.inc language detection (#2279)', () => {
|
||||
describe('detectLanguage', () => {
|
||||
it('reads a directive-only include as Pascal', () => {
|
||||
expect(detectLanguage('src/defs.inc', DIRECTIVES)).toBe('pascal');
|
||||
expect(detectLanguage('src/defs.inc', '{$I other.inc}\n')).toBe('pascal');
|
||||
expect(detectLanguage('src/defs.inc', '{$R-}\n{$Q-}\n')).toBe('pascal');
|
||||
expect(detectLanguage('src/DEFS.INC', '{$define FOO}\n')).toBe('pascal');
|
||||
// Lazarus opens its includes with an IDE marker naming the owning unit.
|
||||
expect(detectLanguage('lcl/include/control.inc', '{%MainUnit ../controls.pp}\n{ comment }\n')).toBe('pascal');
|
||||
});
|
||||
|
||||
it('skips a leading UTF-8 BOM and CRLF line endings', () => {
|
||||
expect(detectLanguage('defs.inc', '\uFEFF{$IFDEF WIN32}\r\n{$DEFINE X}\r\n{$ENDIF}\r\n')).toBe('pascal');
|
||||
expect(detectLanguage('defs.inc', '\uFEFFprocedure Foo;\r\n')).toBe('pascal');
|
||||
});
|
||||
|
||||
it('reads a declaration fragment as Pascal', () => {
|
||||
expect(detectLanguage('r.inc', ROUTINES)).toBe('pascal');
|
||||
expect(detectLanguage('r.inc', 'procedure Foo;\nfunction Bar(A: Integer): Integer;\n')).toBe('pascal');
|
||||
expect(detectLanguage('r.inc', 'procedure TForm1.Button1Click(Sender: TObject);\n')).toBe('pascal');
|
||||
expect(detectLanguage('r.inc', 'const\n X = 1;\n')).toBe('pascal');
|
||||
expect(detectLanguage('r.inc', 'const // ids\n{ Internal functions }\n fpc_in_lo = 1;\n')).toBe('pascal');
|
||||
expect(detectLanguage('r.inc', 'const Max: Integer = 10;\n')).toBe('pascal');
|
||||
expect(detectLanguage('r.inc', 'type\n TColor = (Red, Green);\n')).toBe('pascal');
|
||||
expect(detectLanguage('r.inc', 'type TFoo = class(TObject)\nend;\n')).toBe('pascal');
|
||||
expect(detectLanguage('r.inc', 'var\n G, H: Integer;\n')).toBe('pascal');
|
||||
expect(detectLanguage('r.inc', 'uses\n SysUtils, Classes;\n')).toBe('pascal');
|
||||
expect(detectLanguage('r.inc', 'unit Foo;\ninterface\nimplementation\nend.\n')).toBe('pascal');
|
||||
});
|
||||
|
||||
it('keeps an include with a PHP open tag as PHP', () => {
|
||||
expect(detectLanguage('page.inc', '<?php\nfunction mymodule_help() {}\n')).toBe('php');
|
||||
expect(detectLanguage('page.inc', '\uFEFF <?PHP\nfunction a() {}\n')).toBe('php');
|
||||
expect(detectLanguage('page.inc', '<div><?= $title ?></div>\n')).toBe('php');
|
||||
// A tag anywhere wins over Pascal-looking text elsewhere in the file.
|
||||
expect(detectLanguage('page.inc', '<p>\nprocedure Foo;\n</p>\n<?php echo 1; ?>\n')).toBe('php');
|
||||
});
|
||||
|
||||
it('leaves non-Pascal, untagged includes on the PHP mapping', () => {
|
||||
// C / C++ preprocessor and X-macro tables.
|
||||
expect(detectLanguage('tbl.inc', '#define X 1\n#define Y 2\nconst int z = 3;\n')).toBe('php');
|
||||
expect(detectLanguage('tbl.inc', 'OPCODE(ADD, 0x01)\nOPCODE(SUB, 0x02)\n')).toBe('php');
|
||||
// C++ out-of-line static member definition.
|
||||
expect(detectLanguage('Path.inc', 'const file_t::value_type file_t::Invalid = nullptr;\n')).toBe('php');
|
||||
// SourcePawn typeset members: `function <return type> (<params>);`.
|
||||
expect(
|
||||
detectLanguage('menus.inc', 'typeset MenuHandler\n{\n\tfunction int (Menu menu, MenuAction action);\n\tfunction void (Menu menu);\n};\n'),
|
||||
).toBe('php');
|
||||
// NASM / MASM.
|
||||
expect(detectLanguage('m.inc', '%define STACK 0x1000\n; comment\nfoo equ 1\nmov eax, 1\n')).toBe('php');
|
||||
// POV-Ray.
|
||||
expect(detectLanguage('s.inc', '#declare R = 1;\n#macro M(a)\n#end\n')).toBe('php');
|
||||
// Classic ASP / VBScript server-side include.
|
||||
expect(detectLanguage('a.inc', 'Const X = 1\nFunction Foo(a)\n Foo = a\nEnd Function\n')).toBe('php');
|
||||
// Makefile include.
|
||||
expect(detectLanguage('config.inc', 'CC := gcc\nCFLAGS := -O2\n')).toBe('php');
|
||||
// HTML fragment with inline script, and a Smarty-style `{$var}` line.
|
||||
expect(
|
||||
detectLanguage('f.inc', '<script>\n const x = 1;\n var y = 2;\n function go() { return x; }\n</script>\n{$title}\n'),
|
||||
).toBe('php');
|
||||
// Prose that happens to put Pascal keywords on their own lines.
|
||||
expect(detectLanguage('f.inc', '<li>\n Implementation\n</li>\n<li>\n Begin\n</li>\n')).toBe('php');
|
||||
});
|
||||
|
||||
it('keeps the path-only answer (no source) on PHP', () => {
|
||||
expect(detectLanguage('defs.inc')).toBe('php');
|
||||
expect(detectLanguage('defs.inc', '')).toBe('php');
|
||||
});
|
||||
|
||||
it('lets an explicit codegraph.json mapping for .inc win', () => {
|
||||
expect(detectLanguage('defs.inc', DIRECTIVES, { '.inc': 'php' })).toBe('php');
|
||||
expect(detectLanguage('defs.inc', '#define X 1\n', { '.inc': 'cpp' })).toBe('cpp');
|
||||
expect(detectLanguage('page.inc', '<?php echo 1;', { '.inc': 'pascal' })).toBe('pascal');
|
||||
});
|
||||
});
|
||||
|
||||
describe('grammar preload set', () => {
|
||||
it('loads the Pascal grammar when an .inc file is in the set', () => {
|
||||
expect(preloadLanguagesForFiles(['defs.inc'])).toEqual(expect.arrayContaining(['php', 'pascal']));
|
||||
});
|
||||
|
||||
it('adds nothing for a PHP project without .inc files, or when .inc is mapped explicitly', () => {
|
||||
expect(preloadLanguagesForFiles(['index.php', 'a.module'])).not.toContain('pascal');
|
||||
expect(preloadLanguagesForFiles(['defs.inc'], { '.inc': 'php' })).not.toContain('pascal');
|
||||
});
|
||||
});
|
||||
|
||||
describe('index and sync', () => {
|
||||
let dir: string;
|
||||
beforeEach(() => {
|
||||
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-pascal-inc-'));
|
||||
clearProjectConfigCache();
|
||||
});
|
||||
afterEach(() => {
|
||||
clearProjectConfigCache();
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
const write = (rel: string, body: string) => {
|
||||
const p = path.join(dir, rel);
|
||||
fs.mkdirSync(path.dirname(p), { recursive: true });
|
||||
fs.writeFileSync(p, body);
|
||||
};
|
||||
const languageOf = (cg: CodeGraph, rel: string) => cg.getFiles().find((f) => f.path === rel)?.language;
|
||||
const symbolsIn = (cg: CodeGraph, rel: string) =>
|
||||
cg.getNodesInFile(rel).filter((n) => n.kind !== 'file').map((n) => `${n.kind}:${n.name}:${n.language}`).sort();
|
||||
|
||||
// Runs first, before anything in this file has loaded the Pascal grammar
|
||||
// through a `.pas` file: an include-only tree must still get a parser.
|
||||
it('indexes a tree of Pascal includes with no .pas file', async () => {
|
||||
write('inc/defs.inc', DIRECTIVES);
|
||||
write('inc/routines.inc', ROUTINES);
|
||||
const cg = await CodeGraph.init(dir, { index: true });
|
||||
try {
|
||||
expect(languageOf(cg, 'inc/defs.inc')).toBe('pascal');
|
||||
expect(languageOf(cg, 'inc/routines.inc')).toBe('pascal');
|
||||
expect(symbolsIn(cg, 'inc/routines.inc')).toEqual(['function:Bar:pascal', 'function:Foo:pascal']);
|
||||
expect(cg.getFiles().find((f) => f.path === 'inc/routines.inc')?.errors ?? []).toEqual([]);
|
||||
} finally {
|
||||
cg.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('classifies each include by content, and sync keeps the language', async () => {
|
||||
write('U.pas', 'unit U;\n\ninterface\n\n{$I defs.inc}\n\nprocedure Foo;\n\nimplementation\n\n{$I routines.inc}\n\nend.\n');
|
||||
write('defs.inc', DIRECTIVES);
|
||||
write('routines.inc', ROUTINES);
|
||||
write('drupal/mymodule.inc', '<?php\n\nfunction mymodule_helper() {\n return 1;\n}\n');
|
||||
write('native/table.inc', '#define X 1\n#define Y 2\n');
|
||||
const cg = await CodeGraph.init(dir, { index: true });
|
||||
try {
|
||||
expect(languageOf(cg, 'defs.inc')).toBe('pascal');
|
||||
expect(languageOf(cg, 'routines.inc')).toBe('pascal');
|
||||
expect(languageOf(cg, 'drupal/mymodule.inc')).toBe('php');
|
||||
expect(languageOf(cg, 'native/table.inc')).toBe('php');
|
||||
|
||||
// A directive-only fragment: a file record, no symbols, no errors.
|
||||
expect(symbolsIn(cg, 'defs.inc')).toEqual([]);
|
||||
expect(cg.getFiles().find((f) => f.path === 'defs.inc')?.errors ?? []).toEqual([]);
|
||||
expect(symbolsIn(cg, 'routines.inc')).toEqual(['function:Bar:pascal', 'function:Foo:pascal']);
|
||||
expect(symbolsIn(cg, 'drupal/mymodule.inc')).toEqual(['function:mymodule_helper:php']);
|
||||
|
||||
write('defs.inc', DIRECTIVES + '{$DEFINE USE_FAST_MM}\n');
|
||||
write('routines.inc', ROUTINES + 'procedure Baz;\nbegin\nend;\n');
|
||||
await cg.sync();
|
||||
|
||||
expect(languageOf(cg, 'defs.inc')).toBe('pascal');
|
||||
expect(languageOf(cg, 'routines.inc')).toBe('pascal');
|
||||
expect(symbolsIn(cg, 'routines.inc')).toEqual(['function:Bar:pascal', 'function:Baz:pascal', 'function:Foo:pascal']);
|
||||
expect(languageOf(cg, 'drupal/mymodule.inc')).toBe('php');
|
||||
} finally {
|
||||
cg.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('honors a codegraph.json .inc mapping through index and sync', async () => {
|
||||
write('codegraph.json', JSON.stringify({ extensions: { '.inc': 'php' } }));
|
||||
write('defs.inc', DIRECTIVES);
|
||||
const cg = await CodeGraph.init(dir, { index: true });
|
||||
try {
|
||||
expect(languageOf(cg, 'defs.inc')).toBe('php');
|
||||
write('defs.inc', DIRECTIVES + '{$DEFINE MORE}\n');
|
||||
await cg.sync();
|
||||
expect(languageOf(cg, 'defs.inc')).toBe('php');
|
||||
} finally {
|
||||
cg.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -198,8 +198,12 @@ process.env.CODEGRAPH_KERNEL_LANGS = 'all';
|
||||
for (const { file, lang: extLang } of files) {
|
||||
const source = fs.readFileSync(file, 'utf8');
|
||||
const rel = path.relative(ROOT, file);
|
||||
// `.h` resolves C vs C++ by content — the same call the indexer makes.
|
||||
const lang = extLang === 'detect' || extLang === 'javascript' || extLang === 'jsx' ? detectLanguage(rel, source) : extLang;
|
||||
// `.h` resolves C vs C++ by content — the same call the indexer makes; so
|
||||
// does `.inc` (PHP vs Pascal, #2279 — a Pascal include is not a kernel file).
|
||||
const lang =
|
||||
extLang === 'detect' || extLang === 'javascript' || extLang === 'jsx' || path.extname(file).toLowerCase() === '.inc'
|
||||
? detectLanguage(rel, source)
|
||||
: extLang;
|
||||
if (!KERNEL_LANGS.has(lang)) continue;
|
||||
if (langFilter && !langFilter.has(lang)) continue;
|
||||
processed++;
|
||||
|
||||
@@ -561,9 +561,101 @@ export function detectLanguage(filePath: string, source?: string, overrides?: Re
|
||||
if (looksLikeObjc(source)) return 'objc';
|
||||
}
|
||||
|
||||
// `.inc` is PHP's include extension (Drupal) and Pascal/Delphi's too
|
||||
// (`{$I defs.inc}`: directive blocks, declaration fragments), so it is
|
||||
// decided per file by content (#2279). An explicit codegraph.json mapping
|
||||
// for `.inc` is the user's answer and is not second-guessed.
|
||||
if (lang === 'php' && ext === '.inc' && source && !(overrides && overrides[ext]) && looksLikePascalInclude(source)) {
|
||||
return 'pascal';
|
||||
}
|
||||
|
||||
return lang;
|
||||
}
|
||||
|
||||
/** A PHP open tag: `<?php` in any case, or the short echo `<?=` (never `<?xml`). */
|
||||
const PHP_OPEN_TAG_RE = /<\?(?:php|=)/i;
|
||||
|
||||
// Building blocks for the Pascal line shapes below. A line start allows
|
||||
// indentation and the BOM Windows editors write (a BOM'd Delphi include must
|
||||
// not drop back to PHP). A section keyword's break runs to the end of its line
|
||||
// and over blank / comment-only lines, up to the first declaration.
|
||||
const PAS_LINE = String.raw`^[ \t\uFEFF]*`;
|
||||
const PAS_NAME = String.raw`[a-z_]\w*(?:<[^>\n]*>)?`;
|
||||
const PAS_QNAME = String.raw`[a-z_][\w.]*(?:<[^>\n]*>)?`;
|
||||
const PAS_COMMENT = String.raw`(?:\/\/[^\n]*|\{[^$}\n][^}\n]*\}[ \t]*)?`;
|
||||
const PAS_SECTION_BREAK = String.raw`[ \t]*${PAS_COMMENT}(?:\r?\n[ \t]*${PAS_COMMENT})+`;
|
||||
const pascalLine = (shape: string): RegExp => new RegExp(PAS_LINE + shape, 'im');
|
||||
|
||||
/**
|
||||
* Line shapes only Pascal writes, any one of which makes an untagged `.inc`
|
||||
* Pascal. Each leans on Pascal's own punctuation, so the dialects that share a
|
||||
* keyword with it stay out: JavaScript `const x = 1;` / `function f() {`,
|
||||
* SourcePawn `function void (int client);`, C++ `const T X::Y = …`, VBScript
|
||||
* `Const X = 1` / `Function F(a)`, Smarty `{$var}`, Makefile `X := y`, prose
|
||||
* with `Begin` on a line of its own. Every pattern stays linear on a long
|
||||
* whitespace run: no run can be split two ways between neighbouring
|
||||
* quantifiers.
|
||||
*/
|
||||
const PASCAL_INCLUDE_SIGNALS: readonly RegExp[] = [
|
||||
// A compiler directive: a name and an argument (`{$IFDEF X}`, `{$DEFINE X}`,
|
||||
// `{$I file.inc}`, `{$WARN X OFF}`), a bare `{$ELSE}` / `{$ENDIF}` /
|
||||
// `{$IFEND}`, or a switch (`{$R-}`, `{$A+,B-}`) — or the `{%MainUnit x.pp}`
|
||||
// line Lazarus opens its include files with.
|
||||
pascalLine(String.raw`\{(?:\$(?:[a-z]\w*[ \t]+[^\s}]|(?:else|endif|ifend)[ \t]*\}|[a-z][+-][,}])|%MainUnit\b)`),
|
||||
// A routine header, closed by `;`: `procedure Foo;`, `procedure TForm1.Click(Sender: TObject);`,
|
||||
// `class constructor Create;` — and a function's result type after its
|
||||
// parameters (`function Bar(A: Integer): string;`; bare `function Bar;` is
|
||||
// the implementation-section short form). The parameter list stops at any
|
||||
// parenthesis, so a file of unclosed `procedure X(` lines stays linear.
|
||||
pascalLine(
|
||||
String.raw`(?:class[ \t]+)?(?:(?:procedure|constructor|destructor)[ \t]+${PAS_QNAME}[ \t]*(?:\([^()]*\)[ \t]*)?` +
|
||||
String.raw`|function[ \t]+${PAS_QNAME}[ \t]*(?:(?:\([^()]*\)[ \t]*)?:[ \t]*[\w.]+(?:<[^>\n]*>)?[ \t]*)?);`
|
||||
),
|
||||
// `unit Foo;` and a `uses A, B;` clause.
|
||||
pascalLine(String.raw`unit[ \t]+[a-z_][\w.]*[ \t]*;`),
|
||||
pascalLine(String.raw`uses\s+[a-z_][\w.]*(?:\s*,\s*[a-z_][\w.]*)*\s*;`),
|
||||
// A `const` section, then `X = …` / `X: T = …`; or a typed constant on one
|
||||
// line (`const Max: Integer = 10;`). The type never holds a `:`.
|
||||
pascalLine(
|
||||
String.raw`(?:const|resourcestring)(?:${PAS_SECTION_BREAK}[a-z_]\w*[ \t]*(?::[^=;:\n]+)?=|[ \t]+[a-z_]\w*[ \t]*:[^=;:\n]+=)`
|
||||
),
|
||||
// A `var` section: `G, H: Integer;`, on the keyword's line or below it.
|
||||
pascalLine(
|
||||
String.raw`(?:var|threadvar)(?:${PAS_SECTION_BREAK}|[ \t]+)[a-z_]\w*(?:[ \t]*,[ \t]*[a-z_]\w*)*[ \t]*:(?!:)[^;\n]*;`
|
||||
),
|
||||
// A `type` section, then `TFoo =`; or `type TFoo = class…` (record /
|
||||
// interface / set of / array / procedure type) on one line.
|
||||
pascalLine(
|
||||
String.raw`type(?:${PAS_SECTION_BREAK}${PAS_NAME}[ \t]*=|[ \t]+${PAS_NAME}[ \t]*=[ \t]*(?:packed[ \t]+)?` +
|
||||
String.raw`(?:class|record|object|interface|dispinterface|set[ \t]+of|array|reference[ \t]+to|procedure|function)\b)`
|
||||
),
|
||||
];
|
||||
|
||||
/** A `begin` … `end;` block: both halves needed, so neither alone flips a file. */
|
||||
const PASCAL_BEGIN_RE = pascalLine(String.raw`begin\b`);
|
||||
const PASCAL_END_RE = pascalLine(String.raw`end[ \t]*[;.][ \t]*$`);
|
||||
|
||||
/**
|
||||
* Whether an `.inc` file is a Pascal include rather than a PHP one (#2279).
|
||||
*
|
||||
* A PHP include always opens a PHP tag somewhere, so a tag anywhere keeps the
|
||||
* file PHP. Without one, a Pascal-only line shape (`PASCAL_INCLUDE_SIGNALS`,
|
||||
* or a `begin` … `end;` pair) makes it Pascal. Anything else keeps the PHP
|
||||
* mapping — untagged text is inline HTML to PHP, so nothing is extracted —
|
||||
* rather than handing a C / assembly / POV-Ray / ASP `.inc` to the Pascal
|
||||
* grammar's error recovery.
|
||||
*
|
||||
* Deliberately per file, not "does this project have `.pas` files": the
|
||||
* answer depends only on the file's own bytes, so a full index, a sync of one
|
||||
* edited include, and a fresh re-index always agree — a project-level gate
|
||||
* would flip an untouched include whenever the last `.pas` file came or went.
|
||||
*/
|
||||
function looksLikePascalInclude(source: string): boolean {
|
||||
if (PHP_OPEN_TAG_RE.test(source)) return false;
|
||||
if (PASCAL_INCLUDE_SIGNALS.some((re) => re.test(source))) return true;
|
||||
return PASCAL_BEGIN_RE.test(source) && PASCAL_END_RE.test(source);
|
||||
}
|
||||
|
||||
/** Whether a JavaScript file's leading comments carry Flow's `@flow` pragma (and not `@noflow`). */
|
||||
export function hasFlowPragma(source: string): boolean {
|
||||
const head = source.slice(0, 4096).replace(/^#![^\n]*\n/, '');
|
||||
|
||||
@@ -808,6 +808,15 @@ export function preloadLanguagesForFiles(
|
||||
if (!languages.includes(ambiguous)) languages.push(ambiguous);
|
||||
}
|
||||
}
|
||||
// An `.inc` path-detects as PHP but may read as Pascal (#2279) — unless
|
||||
// codegraph.json maps `.inc` explicitly, which detectLanguage never overrides.
|
||||
if (
|
||||
!languages.includes('pascal') &&
|
||||
!(overrides && overrides['.inc']) &&
|
||||
files.some((f) => f.toLowerCase().endsWith('.inc'))
|
||||
) {
|
||||
languages.push('pascal');
|
||||
}
|
||||
return languages;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user