@birdcc/core 0.0.1-alpha.0 → 0.0.1-alpha.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@birdcc/core",
3
- "version": "0.0.1-alpha.0",
3
+ "version": "0.0.1-alpha.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "GPL-3.0-only",
@@ -9,17 +9,24 @@
9
9
  "email": "npm-dev@birdcc.link",
10
10
  "url": "https://github.com/bird-chinese-community/"
11
11
  },
12
+ "description": "Semantic analysis core for BIRD2 configuration files.",
12
13
  "main": "./dist/index.js",
13
- "types": "./src/index.ts",
14
+ "types": "./dist/index.d.ts",
14
15
  "exports": {
15
16
  ".": {
16
- "types": "./src/index.ts",
17
+ "types": "./dist/index.d.ts",
17
18
  "default": "./dist/index.js"
18
19
  }
19
20
  },
21
+ "sideEffects": false,
22
+ "files": [
23
+ "dist/**",
24
+ "README.md",
25
+ "LICENSE"
26
+ ],
20
27
  "dependencies": {
21
28
  "fast-cidr-tools": "^0.3.4",
22
- "@birdcc/parser": "0.0.1-alpha.0"
29
+ "@birdcc/parser": "0.0.1-alpha.1"
23
30
  },
24
31
  "devDependencies": {
25
32
  "mitata": "^1.0.34"
package/.oxfmtrc.json DELETED
@@ -1,16 +0,0 @@
1
- {
2
- "$schema": "../../../node_modules/oxfmt/configuration_schema.json",
3
- "ignorePatterns": [
4
- "node_modules/*",
5
- "package.json",
6
- "dist/*",
7
- ".turbo/*",
8
- "target/*",
9
- "*.lock",
10
- "pnpm-*.yaml",
11
- "*-lock.json"
12
- ],
13
- "printWidth": 80,
14
- "tabWidth": 2,
15
- "useTabs": false
16
- }
@@ -1,86 +0,0 @@
1
- // Run: pnpm --filter @birdcc/core bench:ip-check
2
-
3
- import { bench, run, summary, barplot } from "mitata";
4
- import { isIP } from "node:net";
5
-
6
- // --- regex implementations ---
7
- // Validates each octet: 0-255, no leading zeros
8
- const IPv4_RE =
9
- /^(25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)(\.(25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)){3}$/;
10
-
11
- // Covers all RFC 4291 compressed forms (::, ::1, 1::, fe80::1, etc.)
12
- // Zone ID (fe80::1%eth0) intentionally excluded — match node:net behavior = include it
13
- const IPv6_RE =
14
- /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]+|::(ffff(:0{1,4})?:)?((25[0-5]|(2[0-4]|1?\d)?\d)\.){3}(25[0-5]|(2[0-4]|1?\d)?\d)|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1?\d)?\d)\.){3}(25[0-5]|(2[0-4]|1?\d)?\d))$/;
15
-
16
- /** Returns 4 | 6 | 0 — same contract as node:net isIP */
17
- const isIP_regex = (input) => {
18
- if (IPv4_RE.test(input)) return 4;
19
- if (IPv6_RE.test(input)) return 6;
20
- return 0;
21
- };
22
-
23
- // --- test fixtures: mix of ipv4 / ipv6 / invalid ---
24
- const samples = [
25
- "127.0.0.1", // valid ipv4
26
- "192.168.1.255", // valid ipv4
27
- "0.0.0.0", // valid ipv4
28
- "255.255.255.255", // valid ipv4
29
- "::1", // valid ipv6 loopback
30
- "2001:db8::1", // valid ipv6
31
- "fe80::1%eth0", // ipv6 with zone id (isIP → 0, regex → 0)
32
- "not-an-ip", // invalid
33
- "127.000.000.001", // invalid (leading zeros)
34
- "999.999.999.999", // invalid (out of range)
35
- ];
36
-
37
- const parseSampleSize = (value) => {
38
- const parsed = Number.parseInt(value ?? "", 10);
39
- if (!Number.isFinite(parsed) || parsed <= 0) {
40
- return 100000;
41
- }
42
- return parsed;
43
- };
44
-
45
- const sampleSize = parseSampleSize(process.env.BENCH_SAMPLE_SIZE);
46
- const samplePool = Array.from({ length: sampleSize }, (_, index) => {
47
- return samples[index % samples.length];
48
- });
49
-
50
- // Sanity-check: both implementations must agree on every sample
51
- for (const s of samples) {
52
- const r = isIP_regex(s);
53
- const n = isIP(s);
54
- if (r !== n) {
55
- throw new Error(`[mismatch] "${s}" regex=${r} node=${n}`);
56
- }
57
- }
58
-
59
- // --- benchmarks ---
60
- summary(() => {
61
- barplot(() => {
62
- bench("regex · single sample (127.0.0.1)", () => isIP_regex("127.0.0.1"));
63
- bench("node · single sample (127.0.0.1)", () => isIP("127.0.0.1"));
64
-
65
- bench("regex · single sample (::1)", () => isIP_regex("::1"));
66
- bench("node · single sample (::1)", () => isIP("::1"));
67
-
68
- bench("regex · single sample (invalid)", () => isIP_regex("not-an-ip"));
69
- bench("node · single sample (invalid)", () => isIP("not-an-ip"));
70
-
71
- bench("regex · 10-sample mixed loop", () => {
72
- for (const s of samples) isIP_regex(s);
73
- });
74
- bench("node · 10-sample mixed loop", () => {
75
- for (const s of samples) isIP(s);
76
- });
77
- bench(`regex · ${sampleSize}-sample deterministic loop`, () => {
78
- for (const s of samplePool) isIP_regex(s);
79
- });
80
- bench(`node · ${sampleSize}-sample deterministic loop`, () => {
81
- for (const s of samplePool) isIP(s);
82
- });
83
- });
84
- });
85
-
86
- await run();
package/src/cross-file.ts DELETED
@@ -1,412 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import { dirname, isAbsolute, normalize, relative, resolve } from "node:path";
3
- import { fileURLToPath, pathToFileURL } from "node:url";
4
- import type { ParsedBirdDocument, SourceRange } from "@birdcc/parser";
5
- import { parseBirdConfig } from "@birdcc/parser";
6
- import type {
7
- BirdDiagnostic,
8
- CoreSnapshot,
9
- CrossFileResolveOptions,
10
- CrossFileResolutionResult,
11
- CrossFileResolutionStats,
12
- SymbolTable,
13
- } from "./types.js";
14
- import { buildCoreSnapshotFromParsed } from "./snapshot.js";
15
- import {
16
- mergeSymbolTables,
17
- pushSymbolTableDiagnostics,
18
- } from "./symbol-table.js";
19
- import { collectCircularTemplateDiagnostics } from "./template-cycles.js";
20
-
21
- export const DEFAULT_CROSS_FILE_MAX_DEPTH = 16;
22
- export const DEFAULT_CROSS_FILE_MAX_FILES = 256;
23
-
24
- const PARSED_DOCUMENT_CACHE_LIMIT = 512;
25
- const parsedDocumentCache = new Map<
26
- string,
27
- { text: string; parsed: ParsedBirdDocument }
28
- >();
29
-
30
- const DEFAULT_RANGE = {
31
- line: 1,
32
- column: 1,
33
- endLine: 1,
34
- endColumn: 1,
35
- } as const;
36
-
37
- const isFileUri = (uri: string): boolean => uri.startsWith("file://");
38
-
39
- const toFilePath = (uri: string): string | null => {
40
- if (isFileUri(uri)) {
41
- return fileURLToPath(uri);
42
- }
43
-
44
- if (uri.startsWith("/")) {
45
- return uri;
46
- }
47
-
48
- return null;
49
- };
50
-
51
- const normalizeUriForPrefixMatch = (uri: string): string =>
52
- uri.replace(/\/+$/, "");
53
-
54
- const toDefaultWorkspaceRootUri = (entryUri: string): string => {
55
- const entryPath = toFilePath(entryUri);
56
- if (entryPath) {
57
- return pathToFileURL(dirname(entryPath)).toString();
58
- }
59
-
60
- return normalize(dirname(entryUri));
61
- };
62
-
63
- const isWithinWorkspaceRoot = (
64
- candidateUri: string,
65
- workspaceRootUri: string,
66
- ): boolean => {
67
- const candidatePath = toFilePath(candidateUri);
68
- const workspaceRootPath = toFilePath(workspaceRootUri);
69
-
70
- if (candidatePath && workspaceRootPath) {
71
- const resolvedRoot = normalize(workspaceRootPath);
72
- const resolvedCandidate = normalize(candidatePath);
73
- const relPath = relative(resolvedRoot, resolvedCandidate);
74
-
75
- return (
76
- relPath.length === 0 ||
77
- (!relPath.startsWith("..") && !isAbsolute(relPath))
78
- );
79
- }
80
-
81
- const normalizedRoot = normalizeUriForPrefixMatch(
82
- normalize(workspaceRootUri),
83
- );
84
- const normalizedCandidate = normalizeUriForPrefixMatch(
85
- normalize(candidateUri),
86
- );
87
- return (
88
- normalizedCandidate === normalizedRoot ||
89
- normalizedCandidate.startsWith(`${normalizedRoot}/`)
90
- );
91
- };
92
-
93
- const defaultReadFileText = async (uri: string): Promise<string> => {
94
- const filePath = toFilePath(uri);
95
- if (!filePath) {
96
- throw new Error(`Unsupported non-file URI '${uri}'`);
97
- }
98
-
99
- return readFile(filePath, "utf8");
100
- };
101
-
102
- const resolveIncludeUri = (baseUri: string, includePath: string): string => {
103
- if (includePath.startsWith("file://")) {
104
- return pathToFileURL(normalize(fileURLToPath(includePath))).toString();
105
- }
106
-
107
- if (isFileUri(baseUri)) {
108
- const basePath = fileURLToPath(baseUri);
109
- const resolvedPath = resolve(dirname(basePath), includePath);
110
- return pathToFileURL(resolvedPath).toString();
111
- }
112
-
113
- return normalize(resolve(dirname(baseUri), includePath));
114
- };
115
-
116
- const includeDiagnostic = (
117
- uri: string,
118
- message: string,
119
- range: SourceRange = DEFAULT_RANGE,
120
- ): BirdDiagnostic => ({
121
- code: "semantic/missing-include",
122
- message,
123
- severity: "warning",
124
- source: "core",
125
- uri,
126
- range: {
127
- line: range.line,
128
- column: range.column,
129
- endLine: range.endLine,
130
- endColumn: range.endColumn,
131
- },
132
- });
133
-
134
- const dedupeDiagnostics = (diagnostics: BirdDiagnostic[]): BirdDiagnostic[] => {
135
- const seen = new Set<string>();
136
- const output: BirdDiagnostic[] = [];
137
-
138
- for (const diagnostic of diagnostics) {
139
- const key = [
140
- diagnostic.code,
141
- diagnostic.message,
142
- diagnostic.uri ?? "",
143
- diagnostic.range.line,
144
- diagnostic.range.column,
145
- diagnostic.range.endLine,
146
- diagnostic.range.endColumn,
147
- ].join(":");
148
-
149
- if (seen.has(key)) {
150
- continue;
151
- }
152
-
153
- seen.add(key);
154
- output.push(diagnostic);
155
- }
156
-
157
- return output;
158
- };
159
-
160
- const parseDocumentWithCache = async (
161
- uri: string,
162
- text: string,
163
- stats: CrossFileResolutionStats,
164
- ): Promise<ParsedBirdDocument> => {
165
- const cached = parsedDocumentCache.get(uri);
166
- if (cached && cached.text === text) {
167
- stats.parsedCacheHits += 1;
168
- return cached.parsed;
169
- }
170
-
171
- stats.parsedCacheMisses += 1;
172
- const parsed = await parseBirdConfig(text);
173
-
174
- if (parsedDocumentCache.size >= PARSED_DOCUMENT_CACHE_LIMIT) {
175
- const oldestKey = parsedDocumentCache.keys().next().value;
176
- if (oldestKey) {
177
- parsedDocumentCache.delete(oldestKey);
178
- }
179
- }
180
-
181
- parsedDocumentCache.set(uri, { text, parsed });
182
- return parsed;
183
- };
184
-
185
- interface QueueItem {
186
- uri: string;
187
- depth: number;
188
- }
189
-
190
- export const resolveCrossFileReferences = async (
191
- options: CrossFileResolveOptions,
192
- ): Promise<CrossFileResolutionResult> => {
193
- const maxDepth = options.maxDepth ?? DEFAULT_CROSS_FILE_MAX_DEPTH;
194
- const maxFiles = options.maxFiles ?? DEFAULT_CROSS_FILE_MAX_FILES;
195
- const loadFromFileSystem = options.loadFromFileSystem ?? true;
196
- const readFileText = options.readFileText ?? defaultReadFileText;
197
- const workspaceRootUri =
198
- options.workspaceRootUri ?? toDefaultWorkspaceRootUri(options.entryUri);
199
- const allowIncludeOutsideWorkspace =
200
- options.allowIncludeOutsideWorkspace ?? false;
201
-
202
- const stats: CrossFileResolutionStats = {
203
- loadedFromMemory: options.documents?.length ?? 0,
204
- loadedFromFileSystem: 0,
205
- skippedByDepth: 0,
206
- skippedByFileLimit: 0,
207
- missingIncludes: 0,
208
- parsedCacheHits: 0,
209
- parsedCacheMisses: 0,
210
- };
211
-
212
- const documentMap = new Map(
213
- (options.documents ?? []).map((document) => [
214
- document.uri,
215
- { uri: document.uri, text: document.text },
216
- ]),
217
- );
218
- const parsedDocuments = new Map<string, ParsedBirdDocument>();
219
- const snapshots: Record<string, CoreSnapshot> = {};
220
- const queue: QueueItem[] = [{ uri: options.entryUri, depth: 0 }];
221
- const queued = new Set<string>([options.entryUri]);
222
- const visited = new Set<string>();
223
- const diagnostics: BirdDiagnostic[] = [];
224
-
225
- const ensureDocument = async (uri: string): Promise<boolean> => {
226
- if (documentMap.has(uri)) {
227
- return true;
228
- }
229
-
230
- if (!loadFromFileSystem) {
231
- return false;
232
- }
233
-
234
- try {
235
- const text = await readFileText(uri);
236
- documentMap.set(uri, { uri, text });
237
- stats.loadedFromFileSystem += 1;
238
- return true;
239
- } catch {
240
- return false;
241
- }
242
- };
243
-
244
- if (!(await ensureDocument(options.entryUri))) {
245
- return {
246
- entryUri: options.entryUri,
247
- visitedUris: [],
248
- symbolTable: { definitions: [], references: [] },
249
- snapshots: {},
250
- documents: {},
251
- diagnostics: [
252
- includeDiagnostic(
253
- options.entryUri,
254
- `Entry file not found or not readable: '${options.entryUri}'`,
255
- ),
256
- ],
257
- stats,
258
- };
259
- }
260
-
261
- while (queue.length > 0) {
262
- const current = queue.shift();
263
- if (!current) {
264
- break;
265
- }
266
-
267
- if (visited.has(current.uri)) {
268
- continue;
269
- }
270
-
271
- if (current.depth > maxDepth) {
272
- stats.skippedByDepth += 1;
273
- continue;
274
- }
275
-
276
- if (visited.size >= maxFiles) {
277
- stats.skippedByFileLimit += 1;
278
- diagnostics.push(
279
- includeDiagnostic(
280
- current.uri,
281
- `Cross-file analysis stopped after reaching max files limit (${maxFiles})`,
282
- ),
283
- );
284
- break;
285
- }
286
-
287
- visited.add(current.uri);
288
-
289
- const document = documentMap.get(current.uri);
290
- if (!document) {
291
- continue;
292
- }
293
-
294
- const parsed = await parseDocumentWithCache(
295
- current.uri,
296
- document.text,
297
- stats,
298
- );
299
- parsedDocuments.set(current.uri, parsed);
300
- snapshots[current.uri] = buildCoreSnapshotFromParsed(parsed, {
301
- uri: current.uri,
302
- typeCheck: options.typeCheck,
303
- });
304
-
305
- for (const declaration of parsed.program.declarations) {
306
- if (declaration.kind !== "include" || declaration.path.length === 0) {
307
- continue;
308
- }
309
-
310
- const includeUri = resolveIncludeUri(current.uri, declaration.path);
311
- const includeRange = declaration.pathRange;
312
-
313
- if (
314
- !allowIncludeOutsideWorkspace &&
315
- !isWithinWorkspaceRoot(includeUri, workspaceRootUri)
316
- ) {
317
- diagnostics.push(
318
- includeDiagnostic(
319
- current.uri,
320
- `Include skipped outside workspace root '${workspaceRootUri}': '${declaration.path}'`,
321
- includeRange,
322
- ),
323
- );
324
- continue;
325
- }
326
-
327
- if (visited.has(includeUri) || queued.has(includeUri)) {
328
- continue;
329
- }
330
-
331
- if (current.depth + 1 > maxDepth) {
332
- stats.skippedByDepth += 1;
333
- diagnostics.push(
334
- includeDiagnostic(
335
- current.uri,
336
- `Include skipped due to max depth (${maxDepth}): '${declaration.path}'`,
337
- includeRange,
338
- ),
339
- );
340
- continue;
341
- }
342
-
343
- if (visited.size + queue.length >= maxFiles) {
344
- stats.skippedByFileLimit += 1;
345
- diagnostics.push(
346
- includeDiagnostic(
347
- current.uri,
348
- `Include skipped due to max files limit (${maxFiles}): '${declaration.path}'`,
349
- includeRange,
350
- ),
351
- );
352
- continue;
353
- }
354
-
355
- const loaded = await ensureDocument(includeUri);
356
- if (!loaded) {
357
- stats.missingIncludes += 1;
358
- diagnostics.push(
359
- includeDiagnostic(
360
- current.uri,
361
- `Included file not found in workspace: '${declaration.path}'`,
362
- includeRange,
363
- ),
364
- );
365
- continue;
366
- }
367
-
368
- queue.push({ uri: includeUri, depth: current.depth + 1 });
369
- queued.add(includeUri);
370
- }
371
- }
372
-
373
- const mergedSymbolTable: SymbolTable = mergeSymbolTables(
374
- Object.values(snapshots).map((snapshot) => snapshot.symbolTable),
375
- );
376
- pushSymbolTableDiagnostics(mergedSymbolTable, diagnostics);
377
-
378
- for (const [uri, snapshot] of Object.entries(snapshots)) {
379
- for (const diagnostic of snapshot.diagnostics) {
380
- if (
381
- diagnostic.code === "semantic/duplicate-definition" ||
382
- diagnostic.code === "semantic/undefined-reference" ||
383
- diagnostic.code === "semantic/circular-template"
384
- ) {
385
- continue;
386
- }
387
-
388
- diagnostics.push({
389
- ...diagnostic,
390
- uri,
391
- });
392
- }
393
- }
394
-
395
- diagnostics.push(
396
- ...collectCircularTemplateDiagnostics(
397
- [...parsedDocuments.entries()].map(([uri, parsed]) => ({ uri, parsed })),
398
- ),
399
- );
400
-
401
- return {
402
- entryUri: options.entryUri,
403
- visitedUris: [...visited],
404
- symbolTable: mergedSymbolTable,
405
- snapshots,
406
- documents: Object.fromEntries(
407
- [...documentMap.entries()].map(([uri, document]) => [uri, document.text]),
408
- ),
409
- diagnostics: dedupeDiagnostics(diagnostics),
410
- stats,
411
- };
412
- };
package/src/index.ts DELETED
@@ -1,42 +0,0 @@
1
- import { parseBirdConfig } from "@birdcc/parser";
2
- import { resolveCrossFileReferences } from "./cross-file.js";
3
- import { buildCoreSnapshotFromParsed } from "./snapshot.js";
4
-
5
- export type {
6
- BirdDiagnostic,
7
- BirdDiagnosticSeverity,
8
- BirdRange,
9
- BirdSymbolKind,
10
- CoreSnapshot,
11
- CrossFileDocumentInput,
12
- CrossFileResolveOptions,
13
- CrossFileResolutionResult,
14
- CrossFileResolutionStats,
15
- SymbolDefinition,
16
- SymbolReference,
17
- SymbolTable,
18
- TypeCheckOptions,
19
- TypeValue,
20
- } from "./types.js";
21
-
22
- export { checkTypes } from "./type-checker.js";
23
- export {
24
- DEFAULT_DOCUMENT_URI,
25
- buildSymbolTableFromParsed,
26
- mergeSymbolTables,
27
- pushSymbolTableDiagnostics,
28
- } from "./symbol-table.js";
29
- export { buildCoreSnapshotFromParsed } from "./snapshot.js";
30
- export {
31
- DEFAULT_CROSS_FILE_MAX_DEPTH,
32
- DEFAULT_CROSS_FILE_MAX_FILES,
33
- resolveCrossFileReferences,
34
- } from "./cross-file.js";
35
-
36
- /** Parses and builds semantic snapshot in one async call. */
37
- export const buildCoreSnapshot = async (text: string) => {
38
- const parsed = await parseBirdConfig(text);
39
- return buildCoreSnapshotFromParsed(parsed);
40
- };
41
-
42
- export type ResolveCrossFileReferences = typeof resolveCrossFileReferences;
package/src/prefix.ts DELETED
@@ -1,94 +0,0 @@
1
- import { isIP } from "node:net";
2
- import { parse as parseCidr } from "fast-cidr-tools";
3
-
4
- const parsePrefixRange = (
5
- suffix: string,
6
- ): { min: number; max: number } | null => {
7
- if (!suffix.startsWith("{") || !suffix.endsWith("}")) {
8
- return null;
9
- }
10
-
11
- const inner = suffix.slice(1, -1);
12
- const commaIndex = inner.indexOf(",");
13
- if (commaIndex <= 0 || commaIndex >= inner.length - 1) {
14
- return null;
15
- }
16
-
17
- const min = Number(inner.slice(0, commaIndex));
18
- const max = Number(inner.slice(commaIndex + 1));
19
-
20
- if (!Number.isInteger(min) || !Number.isInteger(max)) {
21
- return null;
22
- }
23
-
24
- return { min, max };
25
- };
26
-
27
- export const isValidPrefixLiteral = (literal: string): boolean => {
28
- let value = literal.trim();
29
- if (value.length === 0) {
30
- return false;
31
- }
32
-
33
- let range: { min: number; max: number } | null = null;
34
-
35
- if (value.endsWith("+")) {
36
- value = value.slice(0, -1);
37
- } else if (value.endsWith("-")) {
38
- value = value.slice(0, -1);
39
- } else if (value.endsWith("}")) {
40
- const braceStart = value.lastIndexOf("{");
41
- if (braceStart === -1) {
42
- return false;
43
- }
44
-
45
- range = parsePrefixRange(value.slice(braceStart));
46
- if (!range) {
47
- return false;
48
- }
49
-
50
- value = value.slice(0, braceStart);
51
- }
52
-
53
- const slashIndex = value.lastIndexOf("/");
54
- if (slashIndex <= 0 || slashIndex >= value.length - 1) {
55
- return false;
56
- }
57
-
58
- const ipPart = value.slice(0, slashIndex);
59
- const prefixPart = value.slice(slashIndex + 1);
60
- const prefix = Number(prefixPart);
61
-
62
- if (!Number.isInteger(prefix)) {
63
- return false;
64
- }
65
-
66
- const version = isIP(ipPart);
67
- if (version === 0) {
68
- return false;
69
- }
70
-
71
- const maxBits = version === 4 ? 32 : 128;
72
- if (prefix < 0 || prefix > maxBits) {
73
- return false;
74
- }
75
-
76
- if (range) {
77
- if (
78
- range.min < prefix ||
79
- range.min > maxBits ||
80
- range.max < range.min ||
81
- range.max > maxBits
82
- ) {
83
- return false;
84
- }
85
- }
86
-
87
- try {
88
- void parseCidr(value);
89
- } catch {
90
- return false;
91
- }
92
-
93
- return true;
94
- };
package/src/range.ts DELETED
@@ -1,12 +0,0 @@
1
- import type { BirdRange } from "./types.js";
2
-
3
- export const createRange = (
4
- line: number,
5
- column: number,
6
- nameLength = 1,
7
- ): BirdRange => ({
8
- line,
9
- column,
10
- endLine: line,
11
- endColumn: column + Math.max(nameLength, 1),
12
- });