@birdcc/linter 0.0.1-alpha.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/.oxfmtrc.json +16 -0
- package/LICENSE +674 -0
- package/README.md +210 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +93 -0
- package/dist/index.js.map +1 -0
- package/dist/rules/bgp.d.ts +5 -0
- package/dist/rules/bgp.d.ts.map +1 -0
- package/dist/rules/bgp.js +131 -0
- package/dist/rules/bgp.js.map +1 -0
- package/dist/rules/catalog.d.ts +14 -0
- package/dist/rules/catalog.d.ts.map +1 -0
- package/dist/rules/catalog.js +61 -0
- package/dist/rules/catalog.js.map +1 -0
- package/dist/rules/cfg.d.ts +5 -0
- package/dist/rules/cfg.d.ts.map +1 -0
- package/dist/rules/cfg.js +264 -0
- package/dist/rules/cfg.js.map +1 -0
- package/dist/rules/net.d.ts +5 -0
- package/dist/rules/net.d.ts.map +1 -0
- package/dist/rules/net.js +140 -0
- package/dist/rules/net.js.map +1 -0
- package/dist/rules/normalize.d.ts +6 -0
- package/dist/rules/normalize.d.ts.map +1 -0
- package/dist/rules/normalize.js +65 -0
- package/dist/rules/normalize.js.map +1 -0
- package/dist/rules/ospf.d.ts +5 -0
- package/dist/rules/ospf.d.ts.map +1 -0
- package/dist/rules/ospf.js +136 -0
- package/dist/rules/ospf.js.map +1 -0
- package/dist/rules/shared.d.ts +46 -0
- package/dist/rules/shared.d.ts.map +1 -0
- package/dist/rules/shared.js +184 -0
- package/dist/rules/shared.js.map +1 -0
- package/dist/rules/sym.d.ts +5 -0
- package/dist/rules/sym.d.ts.map +1 -0
- package/dist/rules/sym.js +188 -0
- package/dist/rules/sym.js.map +1 -0
- package/dist/rules/type.d.ts +5 -0
- package/dist/rules/type.d.ts.map +1 -0
- package/dist/rules/type.js +130 -0
- package/dist/rules/type.js.map +1 -0
- package/package.json +41 -0
- package/src/index.ts +155 -0
- package/src/rules/bgp.ts +239 -0
- package/src/rules/catalog.ts +80 -0
- package/src/rules/cfg.ts +562 -0
- package/src/rules/net.ts +262 -0
- package/src/rules/normalize.ts +90 -0
- package/src/rules/ospf.ts +221 -0
- package/src/rules/shared.ts +354 -0
- package/src/rules/sym.ts +342 -0
- package/src/rules/type.ts +210 -0
- package/test/linter.bgp-ospf.test.ts +129 -0
- package/test/linter.migration.test.ts +66 -0
- package/test/linter.net-type.test.ts +132 -0
- package/test/linter.sym-cfg.test.ts +224 -0
- package/test/linter.test.ts +21 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { createRuleDiagnostic, filterAndFunctionDeclarations, pushUniqueDiagnostic, scalarTypeOfExpression, } from "./shared.js";
|
|
2
|
+
const normalizeLeftExpression = (value) => value.replace(/^\s*if\s+/i, "").trim();
|
|
3
|
+
const normalizeRightExpression = (value) => value
|
|
4
|
+
.replace(/\bthen\b.*$/i, "")
|
|
5
|
+
.replace(/[;]+$/, "")
|
|
6
|
+
.trim();
|
|
7
|
+
const extractMatches = (text) => {
|
|
8
|
+
const matches = [];
|
|
9
|
+
const pattern = /([^;\n]+?)\s*~\s*([^;\n]+)/g;
|
|
10
|
+
let current = pattern.exec(text);
|
|
11
|
+
while (current) {
|
|
12
|
+
const left = normalizeLeftExpression(current[1] ?? "");
|
|
13
|
+
const right = normalizeRightExpression(current[2] ?? "");
|
|
14
|
+
if (left && right) {
|
|
15
|
+
matches.push({ left, right });
|
|
16
|
+
}
|
|
17
|
+
current = pattern.exec(text);
|
|
18
|
+
}
|
|
19
|
+
return matches;
|
|
20
|
+
};
|
|
21
|
+
const splitSetItems = (setText) => {
|
|
22
|
+
const inner = setText.trim().slice(1, -1).trim();
|
|
23
|
+
if (!inner) {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
return inner
|
|
27
|
+
.split(",")
|
|
28
|
+
.map((item) => item.trim())
|
|
29
|
+
.filter((item) => item.length > 0);
|
|
30
|
+
};
|
|
31
|
+
const statementText = (statement) => {
|
|
32
|
+
if (statement.kind === "expression") {
|
|
33
|
+
return statement.expressionText;
|
|
34
|
+
}
|
|
35
|
+
if (statement.kind === "other") {
|
|
36
|
+
return statement.text;
|
|
37
|
+
}
|
|
38
|
+
if (statement.kind === "if") {
|
|
39
|
+
return statement.conditionText ?? "";
|
|
40
|
+
}
|
|
41
|
+
if (statement.kind === "return") {
|
|
42
|
+
return statement.valueText ?? "";
|
|
43
|
+
}
|
|
44
|
+
return "";
|
|
45
|
+
};
|
|
46
|
+
const declarationText = (source, range) => {
|
|
47
|
+
const lines = source.split(/\r?\n/);
|
|
48
|
+
const startLine = Math.max(1, range.line);
|
|
49
|
+
const endLine = Math.max(startLine, range.endLine);
|
|
50
|
+
return lines.slice(startLine - 1, endLine).join("\n");
|
|
51
|
+
};
|
|
52
|
+
const typeNotIterableRule = ({ parsed }) => {
|
|
53
|
+
const diagnostics = [];
|
|
54
|
+
const seen = new Set();
|
|
55
|
+
for (const declaration of filterAndFunctionDeclarations(parsed)) {
|
|
56
|
+
for (const statement of declaration.statements) {
|
|
57
|
+
const text = statementText(statement);
|
|
58
|
+
if (!text) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
for (const match of extractMatches(text)) {
|
|
62
|
+
if (match.right.startsWith("[") && match.right.endsWith("]")) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
pushUniqueDiagnostic(diagnostics, seen, createRuleDiagnostic("type/not-iterable", `Type '${match.right}' is not iterable in match expression '${match.left} ~ ${match.right}'`, statement));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
for (const match of declaration.matches) {
|
|
69
|
+
const right = normalizeRightExpression(match.right);
|
|
70
|
+
if (right.startsWith("[") && right.endsWith("]")) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
pushUniqueDiagnostic(diagnostics, seen, createRuleDiagnostic("type/not-iterable", `Type '${right}' is not iterable in match expression '${match.left} ${match.operator} ${match.right}'`, match));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return diagnostics;
|
|
77
|
+
};
|
|
78
|
+
const typeSetIncompatibleRule = ({ parsed, text }) => {
|
|
79
|
+
const diagnostics = [];
|
|
80
|
+
const seen = new Set();
|
|
81
|
+
const evaluateSetCompatibility = (left, right, range) => {
|
|
82
|
+
const normalizedRight = normalizeRightExpression(right);
|
|
83
|
+
if (!normalizedRight.startsWith("[") || !normalizedRight.endsWith("]")) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const leftType = scalarTypeOfExpression(left);
|
|
87
|
+
if (leftType === "unknown") {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const items = splitSetItems(normalizedRight);
|
|
91
|
+
if (items.length === 0) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const firstType = scalarTypeOfExpression(items[0] ?? "");
|
|
95
|
+
if (firstType === "unknown" || firstType === leftType) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
pushUniqueDiagnostic(diagnostics, seen, createRuleDiagnostic("type/set-incompatible", `Set-incompatible type (${leftType}) for match set '${normalizedRight}'`, range));
|
|
99
|
+
};
|
|
100
|
+
for (const declaration of filterAndFunctionDeclarations(parsed)) {
|
|
101
|
+
for (const statement of declaration.statements) {
|
|
102
|
+
const text = statementText(statement);
|
|
103
|
+
if (!text) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
for (const match of extractMatches(text)) {
|
|
107
|
+
evaluateSetCompatibility(match.left, match.right, statement);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
for (const match of declaration.matches) {
|
|
111
|
+
evaluateSetCompatibility(match.left, match.right, match);
|
|
112
|
+
}
|
|
113
|
+
const hasTruncatedSetMatch = declaration.matches.some((match) => match.right.includes("[") && !match.right.includes("]"));
|
|
114
|
+
if (hasTruncatedSetMatch) {
|
|
115
|
+
const sourceText = declarationText(text, declaration);
|
|
116
|
+
for (const match of extractMatches(sourceText)) {
|
|
117
|
+
evaluateSetCompatibility(match.left, match.right, declaration);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return diagnostics;
|
|
122
|
+
};
|
|
123
|
+
export const typeRules = [
|
|
124
|
+
typeNotIterableRule,
|
|
125
|
+
typeSetIncompatibleRule,
|
|
126
|
+
];
|
|
127
|
+
export const collectTypeRuleDiagnostics = (context) => {
|
|
128
|
+
return typeRules.flatMap((rule) => rule(context));
|
|
129
|
+
};
|
|
130
|
+
//# sourceMappingURL=type.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type.js","sourceRoot":"","sources":["../../src/rules/type.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@birdcc/linter",
|
|
3
|
+
"version": "0.0.1-alpha.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "GPL-3.0-only",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "BIRD Chinese Community",
|
|
9
|
+
"email": "npm-dev@birdcc.link",
|
|
10
|
+
"url": "https://github.com/bird-chinese-community/"
|
|
11
|
+
},
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./src/index.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./src/index.ts",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@birdcc/core": "0.0.1-alpha.0",
|
|
22
|
+
"@birdcc/parser": "0.0.1-alpha.0"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/bird-chinese-community/BIRD-LSP",
|
|
27
|
+
"directory": "packages/@birdcc/linter"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/bird-chinese-community/BIRD-LSP/blob/main/packages/%40birdcc/linter/README.md",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/bird-chinese-community/BIRD-LSP/issues"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc -p tsconfig.json",
|
|
35
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
36
|
+
"lint": "oxlint --deny-warnings src test",
|
|
37
|
+
"test": "vitest run",
|
|
38
|
+
"format": "oxfmt .",
|
|
39
|
+
"coverage": "vitest run --coverage --coverage.provider=v8 --coverage.reporter=text-summary --coverage.reporter=json-summary"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { CrossFileResolutionResult, SymbolTable } from "@birdcc/core";
|
|
2
|
+
import {
|
|
3
|
+
buildCoreSnapshotFromParsed,
|
|
4
|
+
type BirdDiagnostic,
|
|
5
|
+
type CoreSnapshot,
|
|
6
|
+
} from "@birdcc/core";
|
|
7
|
+
import { parseBirdConfig, type ParsedBirdDocument } from "@birdcc/parser";
|
|
8
|
+
import { collectBgpRuleDiagnostics } from "./rules/bgp.js";
|
|
9
|
+
import { collectCfgRuleDiagnostics } from "./rules/cfg.js";
|
|
10
|
+
import { collectNetRuleDiagnostics } from "./rules/net.js";
|
|
11
|
+
import { normalizeBaseDiagnostics } from "./rules/normalize.js";
|
|
12
|
+
import { collectOspfRuleDiagnostics } from "./rules/ospf.js";
|
|
13
|
+
import { collectSymRuleDiagnostics } from "./rules/sym.js";
|
|
14
|
+
import { type RuleContext } from "./rules/shared.js";
|
|
15
|
+
import { collectTypeRuleDiagnostics } from "./rules/type.js";
|
|
16
|
+
|
|
17
|
+
export interface LintResult {
|
|
18
|
+
parsed: ParsedBirdDocument;
|
|
19
|
+
core: CoreSnapshot;
|
|
20
|
+
diagnostics: BirdDiagnostic[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const dedupeDiagnostics = (diagnostics: BirdDiagnostic[]): BirdDiagnostic[] => {
|
|
24
|
+
const seen = new Set<string>();
|
|
25
|
+
const output: BirdDiagnostic[] = [];
|
|
26
|
+
|
|
27
|
+
for (const diagnostic of diagnostics) {
|
|
28
|
+
const key = [
|
|
29
|
+
diagnostic.code,
|
|
30
|
+
diagnostic.message,
|
|
31
|
+
diagnostic.uri ?? "",
|
|
32
|
+
diagnostic.range.line,
|
|
33
|
+
diagnostic.range.column,
|
|
34
|
+
diagnostic.range.endLine,
|
|
35
|
+
diagnostic.range.endColumn,
|
|
36
|
+
].join(":");
|
|
37
|
+
|
|
38
|
+
if (seen.has(key)) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
seen.add(key);
|
|
43
|
+
output.push(diagnostic);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return output;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const diagnosticsForUri = (
|
|
50
|
+
diagnostics: BirdDiagnostic[],
|
|
51
|
+
uri: string,
|
|
52
|
+
entryUri: string,
|
|
53
|
+
): BirdDiagnostic[] => {
|
|
54
|
+
return diagnostics.filter(
|
|
55
|
+
(diagnostic) => (diagnostic.uri ?? entryUri) === uri,
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const createMergedCoreSnapshot = (
|
|
60
|
+
localCore: CoreSnapshot,
|
|
61
|
+
mergedSymbolTable: SymbolTable,
|
|
62
|
+
scopedDiagnostics: BirdDiagnostic[],
|
|
63
|
+
): CoreSnapshot => ({
|
|
64
|
+
...localCore,
|
|
65
|
+
symbols: mergedSymbolTable.definitions,
|
|
66
|
+
references: mergedSymbolTable.references,
|
|
67
|
+
symbolTable: mergedSymbolTable,
|
|
68
|
+
diagnostics: scopedDiagnostics,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
export interface LintBirdConfigOptions {
|
|
72
|
+
parsed?: ParsedBirdDocument;
|
|
73
|
+
core?: CoreSnapshot;
|
|
74
|
+
uri?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface CrossFileLintResult {
|
|
78
|
+
diagnostics: BirdDiagnostic[];
|
|
79
|
+
byUri: Record<string, LintResult>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Runs parser + core + normalized 32-rule linter pipeline and returns merged diagnostics. */
|
|
83
|
+
export const lintBirdConfig = async (
|
|
84
|
+
text: string,
|
|
85
|
+
options: LintBirdConfigOptions = {},
|
|
86
|
+
): Promise<LintResult> => {
|
|
87
|
+
const parsed = options.parsed ?? (await parseBirdConfig(text));
|
|
88
|
+
const core =
|
|
89
|
+
options.core ?? buildCoreSnapshotFromParsed(parsed, { uri: options.uri });
|
|
90
|
+
|
|
91
|
+
const context: RuleContext = { text, parsed, core };
|
|
92
|
+
|
|
93
|
+
const normalizedBaseDiagnostics = normalizeBaseDiagnostics(
|
|
94
|
+
parsed,
|
|
95
|
+
core.diagnostics,
|
|
96
|
+
{
|
|
97
|
+
uri: options.uri,
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
const ruleDiagnostics: BirdDiagnostic[] = [
|
|
101
|
+
...collectSymRuleDiagnostics(context),
|
|
102
|
+
...collectCfgRuleDiagnostics(context),
|
|
103
|
+
...collectNetRuleDiagnostics(context),
|
|
104
|
+
...collectTypeRuleDiagnostics(context),
|
|
105
|
+
...collectBgpRuleDiagnostics(context),
|
|
106
|
+
...collectOspfRuleDiagnostics(context),
|
|
107
|
+
].map((diagnostic) => ({
|
|
108
|
+
...diagnostic,
|
|
109
|
+
uri: diagnostic.uri ?? options.uri,
|
|
110
|
+
}));
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
parsed,
|
|
114
|
+
core,
|
|
115
|
+
diagnostics: dedupeDiagnostics([
|
|
116
|
+
...normalizedBaseDiagnostics,
|
|
117
|
+
...ruleDiagnostics,
|
|
118
|
+
]),
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const lintResolvedCrossFileGraph = async (
|
|
123
|
+
resolution: CrossFileResolutionResult,
|
|
124
|
+
): Promise<CrossFileLintResult> => {
|
|
125
|
+
const byUri: Record<string, LintResult> = {};
|
|
126
|
+
const diagnostics: BirdDiagnostic[] = [];
|
|
127
|
+
const uris =
|
|
128
|
+
resolution.visitedUris.length > 0
|
|
129
|
+
? resolution.visitedUris
|
|
130
|
+
: [resolution.entryUri];
|
|
131
|
+
|
|
132
|
+
for (const uri of uris) {
|
|
133
|
+
const text = resolution.documents[uri];
|
|
134
|
+
const localCore = resolution.snapshots[uri];
|
|
135
|
+
if (!text || !localCore) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const lintResult = await lintBirdConfig(text, {
|
|
140
|
+
uri,
|
|
141
|
+
core: createMergedCoreSnapshot(
|
|
142
|
+
localCore,
|
|
143
|
+
resolution.symbolTable,
|
|
144
|
+
diagnosticsForUri(resolution.diagnostics, uri, resolution.entryUri),
|
|
145
|
+
),
|
|
146
|
+
});
|
|
147
|
+
byUri[uri] = lintResult;
|
|
148
|
+
diagnostics.push(...lintResult.diagnostics);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
byUri,
|
|
153
|
+
diagnostics: dedupeDiagnostics(diagnostics),
|
|
154
|
+
};
|
|
155
|
+
};
|
package/src/rules/bgp.ts
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import type { BirdDiagnostic } from "@birdcc/core";
|
|
2
|
+
import {
|
|
3
|
+
createProtocolDiagnostic,
|
|
4
|
+
createRuleDiagnostic,
|
|
5
|
+
extractFirstNumberAfterKeyword,
|
|
6
|
+
isProtocolType,
|
|
7
|
+
numericValue,
|
|
8
|
+
protocolDeclarations,
|
|
9
|
+
type BirdRule,
|
|
10
|
+
} from "./shared.js";
|
|
11
|
+
|
|
12
|
+
const isInternalSession = (value: string | undefined): boolean =>
|
|
13
|
+
/^(internal|ibgp)$/i.test(value?.trim() ?? "");
|
|
14
|
+
|
|
15
|
+
const isExternalSession = (value: string | undefined): boolean =>
|
|
16
|
+
/^(external|ebgp)$/i.test(value?.trim() ?? "");
|
|
17
|
+
|
|
18
|
+
const bgpMissingLocalAsRule: BirdRule = ({ parsed }) => {
|
|
19
|
+
const diagnostics: BirdDiagnostic[] = [];
|
|
20
|
+
|
|
21
|
+
for (const declaration of protocolDeclarations(parsed)) {
|
|
22
|
+
if (!isProtocolType(declaration, "bgp")) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (
|
|
27
|
+
declaration.statements.some((statement) => statement.kind === "local-as")
|
|
28
|
+
) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
diagnostics.push(
|
|
33
|
+
createProtocolDiagnostic(
|
|
34
|
+
"bgp/missing-local-as",
|
|
35
|
+
`BGP protocol '${declaration.name}' missing local AS number`,
|
|
36
|
+
declaration,
|
|
37
|
+
),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return diagnostics;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const bgpMissingNeighborRule: BirdRule = ({ parsed }) => {
|
|
45
|
+
const diagnostics: BirdDiagnostic[] = [];
|
|
46
|
+
|
|
47
|
+
for (const declaration of protocolDeclarations(parsed)) {
|
|
48
|
+
if (!isProtocolType(declaration, "bgp")) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (
|
|
53
|
+
declaration.statements.some((statement) => statement.kind === "neighbor")
|
|
54
|
+
) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
diagnostics.push(
|
|
59
|
+
createProtocolDiagnostic(
|
|
60
|
+
"bgp/missing-neighbor",
|
|
61
|
+
`BGP protocol '${declaration.name}' missing neighbor configuration`,
|
|
62
|
+
declaration,
|
|
63
|
+
),
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return diagnostics;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const bgpMissingRemoteAsRule: BirdRule = ({ parsed }) => {
|
|
71
|
+
const diagnostics: BirdDiagnostic[] = [];
|
|
72
|
+
|
|
73
|
+
for (const declaration of protocolDeclarations(parsed)) {
|
|
74
|
+
if (!isProtocolType(declaration, "bgp")) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (const statement of declaration.statements) {
|
|
79
|
+
if (statement.kind !== "neighbor") {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (
|
|
84
|
+
isInternalSession(statement.asn) ||
|
|
85
|
+
isExternalSession(statement.asn)
|
|
86
|
+
) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (statement.asn && numericValue(statement.asn) !== null) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
diagnostics.push(
|
|
95
|
+
createRuleDiagnostic(
|
|
96
|
+
"bgp/missing-remote-as",
|
|
97
|
+
`BGP protocol '${declaration.name}' neighbor '${statement.address}' missing remote AS`,
|
|
98
|
+
statement.asnRange ?? statement.addressRange,
|
|
99
|
+
),
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return diagnostics;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const bgpAsMismatchRule: BirdRule = ({ parsed }) => {
|
|
108
|
+
const diagnostics: BirdDiagnostic[] = [];
|
|
109
|
+
|
|
110
|
+
for (const declaration of protocolDeclarations(parsed)) {
|
|
111
|
+
if (!isProtocolType(declaration, "bgp")) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const localAsStatement = declaration.statements.find(
|
|
116
|
+
(statement) => statement.kind === "local-as",
|
|
117
|
+
);
|
|
118
|
+
if (!localAsStatement || localAsStatement.kind !== "local-as") {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const localAs = numericValue(localAsStatement.asn);
|
|
123
|
+
if (localAs === null) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const hasInternalNeighbor = declaration.statements.some(
|
|
128
|
+
(statement) =>
|
|
129
|
+
statement.kind === "neighbor" && isInternalSession(statement.asn),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
if (!hasInternalNeighbor) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
for (const statement of declaration.statements) {
|
|
137
|
+
if (statement.kind !== "neighbor" || !statement.asn) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (
|
|
142
|
+
isInternalSession(statement.asn) ||
|
|
143
|
+
isExternalSession(statement.asn)
|
|
144
|
+
) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const remoteAs = numericValue(statement.asn);
|
|
149
|
+
if (remoteAs === null || remoteAs === localAs) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
diagnostics.push(
|
|
154
|
+
createRuleDiagnostic(
|
|
155
|
+
"bgp/as-mismatch",
|
|
156
|
+
`BGP protocol '${declaration.name}' internal session requires same ASN (local ${localAs}, remote ${remoteAs})`,
|
|
157
|
+
statement.asnRange ?? statement.addressRange,
|
|
158
|
+
),
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return diagnostics;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const bgpTimerInvalidRule: BirdRule = ({ parsed }) => {
|
|
167
|
+
const diagnostics: BirdDiagnostic[] = [];
|
|
168
|
+
|
|
169
|
+
for (const declaration of protocolDeclarations(parsed)) {
|
|
170
|
+
if (!isProtocolType(declaration, "bgp")) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let hold: number | null = null;
|
|
175
|
+
let keepalive: number | null = null;
|
|
176
|
+
|
|
177
|
+
for (const statement of declaration.statements) {
|
|
178
|
+
if (statement.kind !== "other") {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const text = statement.text.toLowerCase();
|
|
183
|
+
const holdValue = extractFirstNumberAfterKeyword(text, "hold");
|
|
184
|
+
const keepaliveValue = extractFirstNumberAfterKeyword(text, "keepalive");
|
|
185
|
+
|
|
186
|
+
if (holdValue !== null) {
|
|
187
|
+
hold = holdValue;
|
|
188
|
+
if (hold < 3 || hold > 65_535) {
|
|
189
|
+
diagnostics.push(
|
|
190
|
+
createRuleDiagnostic(
|
|
191
|
+
"bgp/timer-invalid",
|
|
192
|
+
`BGP protocol '${declaration.name}' hold time must be in range 3..65535`,
|
|
193
|
+
statement,
|
|
194
|
+
),
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (keepaliveValue !== null) {
|
|
200
|
+
keepalive = keepaliveValue;
|
|
201
|
+
if (keepalive < 1 || keepalive > 65_535) {
|
|
202
|
+
diagnostics.push(
|
|
203
|
+
createRuleDiagnostic(
|
|
204
|
+
"bgp/timer-invalid",
|
|
205
|
+
`BGP protocol '${declaration.name}' keepalive must be in range 1..65535`,
|
|
206
|
+
statement,
|
|
207
|
+
),
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (hold !== null && keepalive !== null && keepalive >= hold) {
|
|
214
|
+
diagnostics.push(
|
|
215
|
+
createProtocolDiagnostic(
|
|
216
|
+
"bgp/timer-invalid",
|
|
217
|
+
`BGP protocol '${declaration.name}' keepalive (${keepalive}) must be smaller than hold (${hold})`,
|
|
218
|
+
declaration,
|
|
219
|
+
),
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return diagnostics;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
export const bgpRules: BirdRule[] = [
|
|
228
|
+
bgpMissingLocalAsRule,
|
|
229
|
+
bgpMissingNeighborRule,
|
|
230
|
+
bgpMissingRemoteAsRule,
|
|
231
|
+
bgpAsMismatchRule,
|
|
232
|
+
bgpTimerInvalidRule,
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
export const collectBgpRuleDiagnostics = (
|
|
236
|
+
context: Parameters<BirdRule>[0],
|
|
237
|
+
): BirdDiagnostic[] => {
|
|
238
|
+
return bgpRules.flatMap((rule) => rule(context));
|
|
239
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { BirdDiagnosticSeverity } from "@birdcc/core";
|
|
2
|
+
|
|
3
|
+
export const RULE_CODES = {
|
|
4
|
+
sym: [
|
|
5
|
+
"sym/undefined",
|
|
6
|
+
"sym/duplicate",
|
|
7
|
+
"sym/proto-type-mismatch",
|
|
8
|
+
"sym/filter-required",
|
|
9
|
+
"sym/function-required",
|
|
10
|
+
"sym/table-required",
|
|
11
|
+
"sym/variable-scope",
|
|
12
|
+
],
|
|
13
|
+
cfg: [
|
|
14
|
+
"cfg/no-protocol",
|
|
15
|
+
"cfg/missing-router-id",
|
|
16
|
+
"cfg/syntax-error",
|
|
17
|
+
"cfg/value-out-of-range",
|
|
18
|
+
"cfg/switch-value-expected",
|
|
19
|
+
"cfg/number-expected",
|
|
20
|
+
"cfg/incompatible-type",
|
|
21
|
+
"cfg/ip-network-mismatch",
|
|
22
|
+
"cfg/circular-template",
|
|
23
|
+
],
|
|
24
|
+
net: [
|
|
25
|
+
"net/invalid-prefix-length",
|
|
26
|
+
"net/invalid-ipv4-prefix",
|
|
27
|
+
"net/invalid-ipv6-prefix",
|
|
28
|
+
"net/max-prefix-length",
|
|
29
|
+
],
|
|
30
|
+
type: ["type/mismatch", "type/not-iterable", "type/set-incompatible"],
|
|
31
|
+
bgp: [
|
|
32
|
+
"bgp/missing-local-as",
|
|
33
|
+
"bgp/missing-neighbor",
|
|
34
|
+
"bgp/missing-remote-as",
|
|
35
|
+
"bgp/as-mismatch",
|
|
36
|
+
"bgp/timer-invalid",
|
|
37
|
+
],
|
|
38
|
+
ospf: [
|
|
39
|
+
"ospf/missing-area",
|
|
40
|
+
"ospf/backbone-stub",
|
|
41
|
+
"ospf/vlink-in-backbone",
|
|
42
|
+
"ospf/asbr-stub-area",
|
|
43
|
+
],
|
|
44
|
+
} as const;
|
|
45
|
+
|
|
46
|
+
export type RuleCode =
|
|
47
|
+
| (typeof RULE_CODES.sym)[number]
|
|
48
|
+
| (typeof RULE_CODES.cfg)[number]
|
|
49
|
+
| (typeof RULE_CODES.net)[number]
|
|
50
|
+
| (typeof RULE_CODES.type)[number]
|
|
51
|
+
| (typeof RULE_CODES.bgp)[number]
|
|
52
|
+
| (typeof RULE_CODES.ospf)[number];
|
|
53
|
+
|
|
54
|
+
const ruleSeverityEntries: ReadonlyArray<
|
|
55
|
+
readonly [RuleCode, BirdDiagnosticSeverity]
|
|
56
|
+
> = [
|
|
57
|
+
...RULE_CODES.sym.map((code) => [code, "error"] as const),
|
|
58
|
+
...RULE_CODES.cfg.map((code) => [code, "error"] as const),
|
|
59
|
+
...RULE_CODES.net.map((code) => [code, "error"] as const),
|
|
60
|
+
...RULE_CODES.type.map((code) => [code, "error"] as const),
|
|
61
|
+
...RULE_CODES.bgp.map((code) => [code, "warning"] as const),
|
|
62
|
+
...RULE_CODES.ospf.map((code) => [code, "warning"] as const),
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
export const RULE_SEVERITY: Record<RuleCode, BirdDiagnosticSeverity> =
|
|
66
|
+
Object.fromEntries(ruleSeverityEntries) as Record<
|
|
67
|
+
RuleCode,
|
|
68
|
+
BirdDiagnosticSeverity
|
|
69
|
+
>;
|
|
70
|
+
|
|
71
|
+
export const LEGACY_CODE_PATTERNS = [
|
|
72
|
+
/^protocol\//,
|
|
73
|
+
/^security\//,
|
|
74
|
+
/^performance\//,
|
|
75
|
+
/^structure\//,
|
|
76
|
+
] as const;
|
|
77
|
+
|
|
78
|
+
export const isRuleCode = (code: string): code is RuleCode => {
|
|
79
|
+
return code in RULE_SEVERITY;
|
|
80
|
+
};
|