@openuji/speculator-lint 0.1.0 → 0.2.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/README.md +13 -168
- package/bin/speculator-lint.js +2 -0
- package/dist/__tests__/config.test.d.ts +2 -0
- package/dist/__tests__/config.test.d.ts.map +1 -0
- package/dist/__tests__/config.test.js +55 -0
- package/dist/__tests__/config.test.js.map +1 -0
- package/dist/__tests__/rules.test.d.ts +2 -0
- package/dist/__tests__/rules.test.d.ts.map +1 -0
- package/dist/__tests__/rules.test.js +410 -0
- package/dist/__tests__/rules.test.js.map +1 -0
- package/dist/cli.js +66 -32
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -4
- package/dist/config.js.map +1 -1
- package/dist/linter.js +1 -1
- package/dist/linter.js.map +1 -1
- package/dist/rule-runner.d.ts +1 -1
- package/dist/rule-runner.d.ts.map +1 -1
- package/dist/rule-runner.js +1 -91
- package/dist/rule-runner.js.map +1 -1
- package/dist/rules/document/no-duplicate-definition.d.ts +9 -0
- package/dist/rules/document/no-duplicate-definition.d.ts.map +1 -0
- package/dist/rules/document/no-duplicate-definition.js +59 -0
- package/dist/rules/document/no-duplicate-definition.js.map +1 -0
- package/dist/rules/index.d.ts +4 -0
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +13 -1
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/reference/no-ambiguous-reference.d.ts +3 -0
- package/dist/rules/reference/no-ambiguous-reference.d.ts.map +1 -0
- package/dist/rules/reference/no-ambiguous-reference.js +59 -0
- package/dist/rules/reference/no-ambiguous-reference.js.map +1 -0
- package/dist/rules/reference/no-id-reference.d.ts +3 -0
- package/dist/rules/reference/no-id-reference.d.ts.map +1 -0
- package/dist/rules/reference/no-id-reference.js +64 -0
- package/dist/rules/reference/no-id-reference.js.map +1 -0
- package/dist/rules/reference/no-unresolved-reference.d.ts +10 -0
- package/dist/rules/reference/no-unresolved-reference.d.ts.map +1 -0
- package/dist/rules/reference/no-unresolved-reference.js +73 -0
- package/dist/rules/reference/no-unresolved-reference.js.map +1 -0
- package/dist/rules/speculator-helpers.d.ts +22 -0
- package/dist/rules/speculator-helpers.d.ts.map +1 -0
- package/dist/rules/speculator-helpers.js +108 -0
- package/dist/rules/speculator-helpers.js.map +1 -0
- package/dist/rules/workspace/no-redefinition.d.ts.map +1 -1
- package/dist/rules/workspace/no-redefinition.js +32 -31
- package/dist/rules/workspace/no-redefinition.js.map +1 -1
- package/dist/rules/workspace/no-reverse-dependency.d.ts.map +1 -1
- package/dist/rules/workspace/no-reverse-dependency.js +30 -19
- package/dist/rules/workspace/no-reverse-dependency.js.map +1 -1
- package/dist/speculator-utils.d.ts +20 -0
- package/dist/speculator-utils.d.ts.map +1 -0
- package/dist/speculator-utils.js +82 -0
- package/dist/speculator-utils.js.map +1 -0
- package/dist/types.d.ts +66 -102
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -5
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +2 -1
- package/dist/utils.js.map +1 -1
- package/package.json +11 -4
package/README.md
CHANGED
|
@@ -1,186 +1,31 @@
|
|
|
1
1
|
# @openuji/speculator-lint
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Developer-grade linting for technical specifications.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`speculator-lint` helps ensure conceptual consistency and semantic integrity in your technical specifications. It catches errors like redefinitions across spec levels, reverse dependencies, and ambiguous references.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
pnpm add -D @openuji/speculator-lint
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Usage
|
|
7
|
+
## 📖 Documentation
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
Lint a workspace AST file:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
speculator-lint workspace.json
|
|
19
|
-
```
|
|
9
|
+
For full documentation on available rules, configuration presets, and extensibility, please visit the [Speculator Documentation](https://speculator.pages.dev/qa/linter).
|
|
20
10
|
|
|
21
|
-
|
|
11
|
+
## 🚀 Quick Start
|
|
22
12
|
|
|
23
13
|
```bash
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
### Programmatic API
|
|
28
|
-
|
|
29
|
-
```typescript
|
|
30
|
-
import { readFileSync } from 'fs';
|
|
31
|
-
import { SpeculatorLinter, builtInRules, recommendedConfig } from '@openuji/speculator-lint';
|
|
32
|
-
|
|
33
|
-
// Load workspace AST
|
|
34
|
-
const workspace = JSON.parse(readFileSync('workspace.json', 'utf-8'));
|
|
35
|
-
|
|
36
|
-
// Build document levels map
|
|
37
|
-
const documentLevels = new Map();
|
|
38
|
-
workspace.documents.forEach((doc, index) => {
|
|
39
|
-
documentLevels.set(doc.sourcePos?.file || '', index);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
// Create linter with built-in rules
|
|
43
|
-
const linter = new SpeculatorLinter(builtInRules);
|
|
44
|
-
|
|
45
|
-
// Run linter
|
|
46
|
-
const result = await linter.lint({
|
|
47
|
-
workspace,
|
|
48
|
-
documentLevels,
|
|
49
|
-
config: recommendedConfig
|
|
50
|
-
});
|
|
14
|
+
# Install
|
|
15
|
+
pnpm add -D @openuji/speculator-lint
|
|
51
16
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
for (const diagnostic of result.diagnostics) {
|
|
55
|
-
console.error(diagnostic.message);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
17
|
+
# Lint your workspace
|
|
18
|
+
speculator-lint workspace.json
|
|
58
19
|
```
|
|
59
20
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
Create a `.speculatorlintrc.json` file in your project root:
|
|
21
|
+
### Basic Config (`.speculatorlintrc.json`)
|
|
63
22
|
|
|
64
23
|
```json
|
|
65
24
|
{
|
|
66
|
-
"extends": ["recommended"]
|
|
67
|
-
"rules": {
|
|
68
|
-
"workspace/no-redefinition": "error",
|
|
69
|
-
"workspace/no-reverse-dependency": "warning"
|
|
70
|
-
}
|
|
25
|
+
"extends": ["recommended"]
|
|
71
26
|
}
|
|
72
27
|
```
|
|
73
28
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
Rules can be configured with:
|
|
77
|
-
- `"off"` - Disable the rule
|
|
78
|
-
- `"error"` - Report as error
|
|
79
|
-
- `"warning"` - Report as warning
|
|
80
|
-
- `"info"` - Report as info
|
|
81
|
-
|
|
82
|
-
### Extends
|
|
83
|
-
|
|
84
|
-
Use `"extends": ["recommended"]` to enable all built-in rules with their default severities.
|
|
85
|
-
|
|
86
|
-
## Built-in Rules
|
|
87
|
-
|
|
88
|
-
### workspace/no-redefinition
|
|
89
|
-
|
|
90
|
-
Ensures that lower-level specs do not redefine concepts from higher-level specs.
|
|
91
|
-
|
|
92
|
-
**Rationale:** In a hierarchical specification system, higher-level specs define the core vocabulary. Lower-level specs should extend, not override these definitions.
|
|
93
|
-
|
|
94
|
-
**Example violation:**
|
|
95
|
-
|
|
96
|
-
```
|
|
97
|
-
core.md (level 0): defines "User"
|
|
98
|
-
extension.md (level 1): defines "User" again ← ERROR
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### workspace/no-reverse-dependency
|
|
102
|
-
|
|
103
|
-
Ensures that higher-level specs do not depend on (reference) lower-level specs.
|
|
104
|
-
|
|
105
|
-
**Rationale:** Dependencies should flow downward in the hierarchy. Higher-level specs should be self-contained and not rely on lower-level implementation details.
|
|
106
|
-
|
|
107
|
-
**Example violation:**
|
|
108
|
-
|
|
109
|
-
```
|
|
110
|
-
core.md (level 0): references "DetailedConfig"
|
|
111
|
-
extension.md (level 1): defines "DetailedConfig" ← ERROR
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
## Creating Custom Rules
|
|
115
|
-
|
|
116
|
-
You can create custom rules by implementing the `LintRule` interface:
|
|
117
|
-
|
|
118
|
-
```typescript
|
|
119
|
-
import type { LintRule } from '@openuji/speculator-lint';
|
|
120
|
-
|
|
121
|
-
export const myCustomRule: LintRule = {
|
|
122
|
-
meta: {
|
|
123
|
-
name: 'my-custom-rule',
|
|
124
|
-
code: 'my-custom-rule',
|
|
125
|
-
severity: 'warning',
|
|
126
|
-
description: 'My custom validation rule',
|
|
127
|
-
category: 'custom'
|
|
128
|
-
},
|
|
129
|
-
|
|
130
|
-
create(context) {
|
|
131
|
-
return {
|
|
132
|
-
// Called for each definition
|
|
133
|
-
onDefinition(entry, allEntriesForTerm) {
|
|
134
|
-
// Your validation logic
|
|
135
|
-
if (/* some condition */) {
|
|
136
|
-
context.report({
|
|
137
|
-
message: 'Issue found',
|
|
138
|
-
file: entry.sourcePos?.file,
|
|
139
|
-
sourcePos: entry.sourcePos
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
},
|
|
143
|
-
|
|
144
|
-
// Called for each reference
|
|
145
|
-
onReference(ref, target) {
|
|
146
|
-
// Your validation logic
|
|
147
|
-
},
|
|
148
|
-
|
|
149
|
-
// Called once per document
|
|
150
|
-
onDocument(doc) {
|
|
151
|
-
// Your validation logic
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
// Use with linter
|
|
158
|
-
const linter = new SpeculatorLinter([...builtInRules, myCustomRule]);
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
## API Reference
|
|
162
|
-
|
|
163
|
-
### SpeculatorLinter
|
|
164
|
-
|
|
165
|
-
Main linter class.
|
|
166
|
-
|
|
167
|
-
```typescript
|
|
168
|
-
class SpeculatorLinter {
|
|
169
|
-
constructor(rules: LintRule[]);
|
|
170
|
-
lint(options: LintOptions): Promise<LintResult>;
|
|
171
|
-
getRules(): LintRule[];
|
|
172
|
-
}
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
### Types
|
|
176
|
-
|
|
177
|
-
- `LintRule` - Rule interface
|
|
178
|
-
- `LintContext` - Context provided to rules
|
|
179
|
-
- `LintVisitor` - Visitor pattern for AST traversal
|
|
180
|
-
- `LintDiagnostic` - Diagnostic output
|
|
181
|
-
- `LintResult` - Lint result with diagnostics
|
|
182
|
-
- `LintConfig` - Configuration schema
|
|
183
|
-
|
|
184
|
-
## License
|
|
29
|
+
---
|
|
185
30
|
|
|
186
|
-
|
|
31
|
+
Part of the [Speculator](https://github.com/openuji/speculator) ecosystem.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { normalizeConfig, recommendedConfig } from '../config.js';
|
|
3
|
+
describe('Configuration System', () => {
|
|
4
|
+
it('should return the same config if no extends are present', () => {
|
|
5
|
+
const config = {
|
|
6
|
+
rules: {
|
|
7
|
+
'document/no-duplicate-definition': 'warning'
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
const normalized = normalizeConfig(config);
|
|
11
|
+
expect(normalized.rules).toEqual(config.rules);
|
|
12
|
+
});
|
|
13
|
+
it('should merge with recommended config when extends includes "recommended"', () => {
|
|
14
|
+
const config = {
|
|
15
|
+
extends: ['recommended'],
|
|
16
|
+
rules: {}
|
|
17
|
+
};
|
|
18
|
+
const normalized = normalizeConfig(config);
|
|
19
|
+
// Should have all recommended rules
|
|
20
|
+
expect(normalized.rules).toEqual(recommendedConfig.rules);
|
|
21
|
+
});
|
|
22
|
+
it('should allow overriding recommended rules', () => {
|
|
23
|
+
const config = {
|
|
24
|
+
extends: ['recommended'],
|
|
25
|
+
rules: {
|
|
26
|
+
'reference/no-id-reference': 'error'
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const normalized = normalizeConfig(config);
|
|
30
|
+
// Should have the override
|
|
31
|
+
expect(normalized.rules['reference/no-id-reference']).toBe('error');
|
|
32
|
+
// Should still have other recommended rules
|
|
33
|
+
expect(normalized.rules['workspace/no-redefinition']).toBe('error');
|
|
34
|
+
expect(normalized.rules['reference/no-ambiguous-reference']).toBe('warning');
|
|
35
|
+
});
|
|
36
|
+
it('should allow disabling recommended rules', () => {
|
|
37
|
+
const config = {
|
|
38
|
+
extends: ['recommended'],
|
|
39
|
+
rules: {
|
|
40
|
+
'workspace/no-redefinition': 'off'
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const normalized = normalizeConfig(config);
|
|
44
|
+
expect(normalized.rules['workspace/no-redefinition']).toBe('off');
|
|
45
|
+
});
|
|
46
|
+
it('should handle multiple extends if added in future (currently only "recommended")', () => {
|
|
47
|
+
const config = {
|
|
48
|
+
extends: ['recommended', 'unknown-preset'],
|
|
49
|
+
rules: {}
|
|
50
|
+
};
|
|
51
|
+
const normalized = normalizeConfig(config);
|
|
52
|
+
expect(normalized.rules).toEqual(recommendedConfig.rules);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
//# sourceMappingURL=config.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGlE,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QAC/D,MAAM,MAAM,GAAe;YACvB,KAAK,EAAE;gBACH,kCAAkC,EAAE,SAAS;aAChD;SACJ,CAAC;QACF,MAAM,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;QAC3C,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0EAA0E,EAAE,GAAG,EAAE;QAChF,MAAM,MAAM,GAAe;YACvB,OAAO,EAAE,CAAC,aAAa,CAAC;YACxB,KAAK,EAAE,EAAE;SACZ,CAAC;QACF,MAAM,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;QAE3C,oCAAoC;QACpC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACjD,MAAM,MAAM,GAAe;YACvB,OAAO,EAAE,CAAC,aAAa,CAAC;YACxB,KAAK,EAAE;gBACH,2BAA2B,EAAE,OAAO;aACvC;SACJ,CAAC;QACF,MAAM,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;QAE3C,2BAA2B;QAC3B,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAEpE,4CAA4C;QAC5C,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpE,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACjF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAChD,MAAM,MAAM,GAAe;YACvB,OAAO,EAAE,CAAC,aAAa,CAAC;YACxB,KAAK,EAAE;gBACH,2BAA2B,EAAE,KAAK;aACrC;SACJ,CAAC;QACF,MAAM,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;QAE3C,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kFAAkF,EAAE,GAAG,EAAE;QACvF,MAAM,MAAM,GAAe;YACxB,OAAO,EAAE,CAAC,aAAa,EAAE,gBAAgB,CAAC;YAC1C,KAAK,EAAE,EAAE;SACZ,CAAC;QACF,MAAM,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;QAC3C,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/rules.test.ts"],"names":[],"mappings":""}
|