@mrclrchtr/supi-code-runtime 10.0.0 → 11.0.0
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.
- package/package.json +4 -1
- package/src/api.ts +11 -0
- package/src/file-associations.ts +190 -0
- package/src/types.ts +22 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-code-runtime",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "11.0.0",
|
|
4
4
|
"description": "Shared workspace context and capability contracts for code intelligence",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -28,6 +28,9 @@
|
|
|
28
28
|
"src/**/*.ts",
|
|
29
29
|
"!__tests__"
|
|
30
30
|
],
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"picomatch": "4.0.7"
|
|
33
|
+
},
|
|
31
34
|
"main": "src/api.ts",
|
|
32
35
|
"exports": {
|
|
33
36
|
"./api": "./src/api.ts",
|
package/src/api.ts
CHANGED
|
@@ -14,6 +14,17 @@ export type {
|
|
|
14
14
|
StructuralProvider,
|
|
15
15
|
StructuralResult,
|
|
16
16
|
} from "./capability/types.ts";
|
|
17
|
+
export type {
|
|
18
|
+
CFamilyExtension,
|
|
19
|
+
CFamilyFileAssociationResolver,
|
|
20
|
+
CFamilyFileAssociationRule,
|
|
21
|
+
CFamilyLanguage,
|
|
22
|
+
} from "./file-associations.ts";
|
|
23
|
+
export {
|
|
24
|
+
C_FAMILY_EXTENSIONS,
|
|
25
|
+
createCFamilyFileAssociationResolver,
|
|
26
|
+
defaultCFamilyLanguage,
|
|
27
|
+
} from "./file-associations.ts";
|
|
17
28
|
export type { CodeQueryPartialMetadata, CodeQueryResult } from "./query-result.ts";
|
|
18
29
|
// Shared query-result constructors and contract
|
|
19
30
|
export {
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import picomatch from "picomatch";
|
|
3
|
+
|
|
4
|
+
/** Language selected for a C-family source file. */
|
|
5
|
+
export type CFamilyLanguage = "c" | "cpp";
|
|
6
|
+
|
|
7
|
+
/** One ordered, project-relative language rule for an existing C-family file. */
|
|
8
|
+
export interface CFamilyFileAssociationRule {
|
|
9
|
+
readonly pattern: string;
|
|
10
|
+
readonly language: CFamilyLanguage;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** C-family suffixes that already have Tree-sitter and LSP support. */
|
|
14
|
+
export const C_FAMILY_EXTENSIONS = Object.freeze([
|
|
15
|
+
".c",
|
|
16
|
+
".h",
|
|
17
|
+
".cpp",
|
|
18
|
+
".hpp",
|
|
19
|
+
".cc",
|
|
20
|
+
".cxx",
|
|
21
|
+
".hxx",
|
|
22
|
+
".c++",
|
|
23
|
+
".h++",
|
|
24
|
+
] as const);
|
|
25
|
+
|
|
26
|
+
export type CFamilyExtension = (typeof C_FAMILY_EXTENSIONS)[number];
|
|
27
|
+
|
|
28
|
+
const DEFAULT_C_FAMILY_LANGUAGES: Readonly<Record<CFamilyExtension, CFamilyLanguage>> =
|
|
29
|
+
Object.freeze({
|
|
30
|
+
".c": "c",
|
|
31
|
+
".h": "c",
|
|
32
|
+
".cpp": "cpp",
|
|
33
|
+
".hpp": "cpp",
|
|
34
|
+
".cc": "cpp",
|
|
35
|
+
".cxx": "cpp",
|
|
36
|
+
".hxx": "cpp",
|
|
37
|
+
".c++": "cpp",
|
|
38
|
+
".h++": "cpp",
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/** Immutable C-family language selection for one workspace. */
|
|
42
|
+
export interface CFamilyFileAssociationResolver {
|
|
43
|
+
/** Absolute root used to make project-relative paths. */
|
|
44
|
+
readonly workspaceRoot: string;
|
|
45
|
+
/** Validated ordered rules captured when the workspace host starts. */
|
|
46
|
+
readonly rules: readonly CFamilyFileAssociationRule[];
|
|
47
|
+
/** Select the configured language or the existing suffix default. */
|
|
48
|
+
languageForFile(filePath: string): CFamilyLanguage | undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Return the current default language for one supported C-family suffix. */
|
|
52
|
+
export function defaultCFamilyLanguage(extension: string): CFamilyLanguage | undefined {
|
|
53
|
+
return DEFAULT_C_FAMILY_LANGUAGES[extension.toLowerCase() as CFamilyExtension];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Validate and compile ordered C-family file associations for one workspace.
|
|
58
|
+
* Patterns are case-sensitive globs over POSIX-style, workspace-relative paths.
|
|
59
|
+
* Supported patterns use `*`, `?`, `**`, character classes, and brace alternatives.
|
|
60
|
+
* The first matching rule wins. Rules never expand the eligible suffix set.
|
|
61
|
+
*
|
|
62
|
+
* @throws If the rule list, a rule, a language, or a pattern is invalid.
|
|
63
|
+
*/
|
|
64
|
+
export function createCFamilyFileAssociationResolver(
|
|
65
|
+
workspaceRoot: string,
|
|
66
|
+
input: unknown = [],
|
|
67
|
+
): CFamilyFileAssociationResolver {
|
|
68
|
+
const rules = validateRules(input);
|
|
69
|
+
const matchers = rules.map((rule, index) => ({
|
|
70
|
+
language: rule.language,
|
|
71
|
+
matches: compilePattern(rule.pattern, `code-intelligence.fileAssociations[${index}]`),
|
|
72
|
+
}));
|
|
73
|
+
const root = path.resolve(workspaceRoot);
|
|
74
|
+
return Object.freeze({
|
|
75
|
+
workspaceRoot: root,
|
|
76
|
+
rules,
|
|
77
|
+
languageForFile(filePath: string): CFamilyLanguage | undefined {
|
|
78
|
+
const extension = normalizeExtension(filePath);
|
|
79
|
+
const fallback = defaultCFamilyLanguage(extension);
|
|
80
|
+
if (!fallback) return undefined;
|
|
81
|
+
|
|
82
|
+
const absolutePath = path.resolve(root, filePath);
|
|
83
|
+
const relativePath = toPosixPath(path.relative(root, absolutePath));
|
|
84
|
+
if (
|
|
85
|
+
relativePath === ".." ||
|
|
86
|
+
relativePath.startsWith("../") ||
|
|
87
|
+
path.posix.isAbsolute(relativePath)
|
|
88
|
+
) {
|
|
89
|
+
return fallback;
|
|
90
|
+
}
|
|
91
|
+
for (const rule of matchers) {
|
|
92
|
+
if (rule.matches(relativePath)) return rule.language;
|
|
93
|
+
}
|
|
94
|
+
return fallback;
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function validateRules(input: unknown): readonly CFamilyFileAssociationRule[] {
|
|
100
|
+
if (!Array.isArray(input)) {
|
|
101
|
+
throw new Error('Invalid "code-intelligence.fileAssociations": expected an ordered array.');
|
|
102
|
+
}
|
|
103
|
+
if (input.length > 64) {
|
|
104
|
+
throw new Error('Invalid "code-intelligence.fileAssociations": use no more than 64 rules.');
|
|
105
|
+
}
|
|
106
|
+
const rules: CFamilyFileAssociationRule[] = [];
|
|
107
|
+
const seenPatterns = new Set<string>();
|
|
108
|
+
for (const [index, value] of input.entries()) {
|
|
109
|
+
const key = `code-intelligence.fileAssociations[${index}]`;
|
|
110
|
+
if (!isRecord(value) || !hasExactKeys(value, ["pattern", "language"])) {
|
|
111
|
+
throw new Error(`${key} must contain only "pattern" and "language" fields.`);
|
|
112
|
+
}
|
|
113
|
+
if (typeof value.pattern !== "string") {
|
|
114
|
+
throw new Error(`${key}.pattern must be a string.`);
|
|
115
|
+
}
|
|
116
|
+
if (value.language !== "c" && value.language !== "cpp") {
|
|
117
|
+
throw new Error(`${key}.language must be "c" or "cpp".`);
|
|
118
|
+
}
|
|
119
|
+
if (seenPatterns.has(value.pattern)) {
|
|
120
|
+
throw new Error(`${key}.pattern duplicates an earlier rule.`);
|
|
121
|
+
}
|
|
122
|
+
seenPatterns.add(value.pattern);
|
|
123
|
+
rules.push(Object.freeze({ pattern: value.pattern, language: value.language }));
|
|
124
|
+
}
|
|
125
|
+
return Object.freeze(rules);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function compilePattern(pattern: string, key: string): (file: string) => boolean {
|
|
129
|
+
if (
|
|
130
|
+
pattern.length === 0 ||
|
|
131
|
+
pattern.length > 512 ||
|
|
132
|
+
pattern !== pattern.trim() ||
|
|
133
|
+
pattern.includes("\\") ||
|
|
134
|
+
hasControlCharacter(pattern) ||
|
|
135
|
+
pattern.startsWith("/") ||
|
|
136
|
+
/^[A-Za-z]:/.test(pattern) ||
|
|
137
|
+
pattern.startsWith("!")
|
|
138
|
+
) {
|
|
139
|
+
throw new Error(`${key}.pattern must be a safe workspace-relative glob using "/" separators.`);
|
|
140
|
+
}
|
|
141
|
+
const segments = pattern.split("/");
|
|
142
|
+
if (segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
|
|
143
|
+
throw new Error(`${key}.pattern contains an unsafe or unsupported path pattern.`);
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
return picomatch(pattern, {
|
|
147
|
+
nocase: false,
|
|
148
|
+
windows: false,
|
|
149
|
+
dot: true,
|
|
150
|
+
strictBrackets: true,
|
|
151
|
+
nonegate: true,
|
|
152
|
+
noextglob: true,
|
|
153
|
+
maxLength: 512,
|
|
154
|
+
});
|
|
155
|
+
} catch (error) {
|
|
156
|
+
throw new Error(`${key}.pattern is not a valid glob: ${errorMessage(error)}`, { cause: error });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function hasControlCharacter(value: string): boolean {
|
|
161
|
+
for (const character of value) {
|
|
162
|
+
const code = character.charCodeAt(0);
|
|
163
|
+
if (code < 32 || code === 127) return true;
|
|
164
|
+
}
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function normalizeExtension(filePath: string): string {
|
|
169
|
+
return path.posix.extname(toPosixPath(filePath)).toLowerCase();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function toPosixPath(value: string): string {
|
|
173
|
+
return value.replaceAll("\\", "/");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
177
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function hasExactKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {
|
|
181
|
+
const allowed = new Set(keys);
|
|
182
|
+
return (
|
|
183
|
+
keys.every((key) => Object.hasOwn(value, key)) &&
|
|
184
|
+
Object.keys(value).every((key) => allowed.has(key))
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function errorMessage(error: unknown): string {
|
|
189
|
+
return error instanceof Error ? error.message : String(error);
|
|
190
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -80,6 +80,14 @@ export interface DocumentCodeSymbol extends CodeSymbol {
|
|
|
80
80
|
*/
|
|
81
81
|
export type CodeResult<T> =
|
|
82
82
|
| { kind: "success"; data: T }
|
|
83
|
+
| {
|
|
84
|
+
kind: "partial";
|
|
85
|
+
data: T;
|
|
86
|
+
file: string;
|
|
87
|
+
reason: string;
|
|
88
|
+
/** Syntax error and missing-node ranges in 0-based UTF-16 coordinates. */
|
|
89
|
+
errorRanges: SourceRange[];
|
|
90
|
+
}
|
|
83
91
|
| { kind: "unsupported-language"; file: string; message: string }
|
|
84
92
|
| { kind: "file-access-error"; file: string; message: string }
|
|
85
93
|
| { kind: "validation-error"; message: string }
|
|
@@ -93,7 +101,10 @@ export type ConfidenceMode = "semantic" | "structural" | "heuristic" | "unavaila
|
|
|
93
101
|
|
|
94
102
|
export interface OutlineData {
|
|
95
103
|
name: string;
|
|
104
|
+
/** Common structural search category. */
|
|
96
105
|
kind: string;
|
|
106
|
+
/** Source-language declaration kind, separate from common search category. */
|
|
107
|
+
nativeKind?: string;
|
|
97
108
|
/** Exact 1-based start of the declaration name token, when known. */
|
|
98
109
|
nameAnchor?: SymbolAnchor;
|
|
99
110
|
/** Exclusive 1-based end of a verified literal name span, when known. */
|
|
@@ -156,6 +167,12 @@ export interface FileEdit {
|
|
|
156
167
|
/** A document-state precondition established when semantic edits are normalized. */
|
|
157
168
|
export type DocumentEditPrecondition =
|
|
158
169
|
| { file: string; kind: "open-document-version"; version: number }
|
|
170
|
+
| {
|
|
171
|
+
file: string;
|
|
172
|
+
kind: "open-document-content";
|
|
173
|
+
version: number;
|
|
174
|
+
fingerprint: string;
|
|
175
|
+
}
|
|
159
176
|
| { file: string; kind: "disk-content" };
|
|
160
177
|
|
|
161
178
|
/**
|
|
@@ -257,5 +274,10 @@ export interface CalleesData {
|
|
|
257
274
|
/** A single call-site match with name and start line. */
|
|
258
275
|
export interface CallSite {
|
|
259
276
|
name: string;
|
|
277
|
+
/** 1-based UTF-16 start position of the written callee expression. */
|
|
260
278
|
startLine: number;
|
|
279
|
+
startCharacter: number;
|
|
280
|
+
/** Exclusive 1-based UTF-16 end position of the captured source expression. */
|
|
281
|
+
endLine: number;
|
|
282
|
+
endCharacter: number;
|
|
261
283
|
}
|