|
1 | 1 | import { getFixes } from './fix.js'; |
2 | | -import { SOURCE_CODE } from './source_code.js'; |
| 2 | +import { getIndexFromLoc, SOURCE_CODE } from './source_code.js'; |
3 | 3 |
|
4 | 4 | import type { Fix, FixFn } from './fix.ts'; |
5 | 5 | import type { SourceCode } from './source_code.ts'; |
6 | | -import type { Node } from './types.ts'; |
| 6 | +import type { Location, Node } from './types.ts'; |
| 7 | + |
| 8 | +const { hasOwn } = Object; |
7 | 9 |
|
8 | 10 | // Diagnostic in form passed by user to `Context#report()` |
9 | | -export interface Diagnostic { |
| 11 | +export type Diagnostic = DiagnosticWithNode | DiagnosticWithLoc; |
| 12 | + |
| 13 | +export interface DiagnosticBase { |
10 | 14 | message: string; |
11 | | - node: Node; |
12 | 15 | fix?: FixFn; |
13 | 16 | } |
14 | 17 |
|
| 18 | +export interface DiagnosticWithNode extends DiagnosticBase { |
| 19 | + node: Node; |
| 20 | +} |
| 21 | + |
| 22 | +export interface DiagnosticWithLoc extends DiagnosticBase { |
| 23 | + loc: Location; |
| 24 | +} |
| 25 | + |
15 | 26 | // Diagnostic in form sent to Rust |
16 | 27 | interface DiagnosticReport { |
17 | 28 | message: string; |
@@ -127,16 +138,38 @@ export class Context { |
127 | 138 | /** |
128 | 139 | * Report error. |
129 | 140 | * @param diagnostic - Diagnostic object |
| 141 | + * @throws {TypeError} If `diagnostic` is invalid |
130 | 142 | */ |
131 | 143 | report(diagnostic: Diagnostic): void { |
132 | 144 | const internal = getInternal(this, 'report errors'); |
133 | 145 |
|
134 | 146 | // TODO: Validate `diagnostic` |
135 | | - const { node } = diagnostic; |
| 147 | + let start: number, end: number, loc: Location; |
| 148 | + if (hasOwn(diagnostic, 'loc') && (loc = (diagnostic as DiagnosticWithLoc).loc) != null) { |
| 149 | + // `loc` |
| 150 | + if (typeof loc !== 'object') throw new TypeError('`loc` must be an object'); |
| 151 | + start = getIndexFromLoc(loc.start); |
| 152 | + end = getIndexFromLoc(loc.end); |
| 153 | + } else { |
| 154 | + // `node` |
| 155 | + const { node } = diagnostic as DiagnosticWithNode; |
| 156 | + if (node == null) throw new TypeError('Either `node` or `loc` is required'); |
| 157 | + if (typeof node !== 'object') throw new TypeError('`node` must be an object'); |
| 158 | + ({ start, end } = node); |
| 159 | + // Do type validation checks here, to ensure no error in serialization / deserialization. |
| 160 | + // Range validation happens on Rust side. |
| 161 | + if ( |
| 162 | + typeof start !== 'number' || typeof end !== 'number' || |
| 163 | + start < 0 || end < 0 || (start | 0) !== start || (end | 0) !== end |
| 164 | + ) { |
| 165 | + throw new TypeError('`node.start` and `node.end` must be non-negative integers'); |
| 166 | + } |
| 167 | + } |
| 168 | + |
136 | 169 | diagnostics.push({ |
137 | 170 | message: diagnostic.message, |
138 | | - start: node.start, |
139 | | - end: node.end, |
| 171 | + start, |
| 172 | + end, |
140 | 173 | ruleIndex: internal.ruleIndex, |
141 | 174 | fixes: getFixes(diagnostic, internal), |
142 | 175 | }); |
|
0 commit comments