|
26 | 26 | * and global variables (`filePath`, `settings`, `cwd`). |
27 | 27 | */ |
28 | 28 |
|
29 | | -import { getFixes } from './fix.js'; |
30 | | -import { getOffsetFromLineColumn } from './location.js'; |
31 | 29 | import { ast, initAst, SOURCE_CODE } from './source_code.js'; |
| 30 | +import { report } from './report.js'; |
32 | 31 | import { settings, initSettings } from './settings.js'; |
33 | 32 |
|
34 | | -import type { Fix, FixFn } from './fix.ts'; |
35 | 33 | import type { RuleDetails } from './load.ts'; |
| 34 | +import type { Diagnostic } from './report.ts'; |
36 | 35 | import type { SourceCode } from './source_code.ts'; |
37 | | -import type { Location, Ranged } from './location.ts'; |
38 | 36 | import type { ModuleKind } from '../generated/types.d.ts'; |
39 | 37 |
|
40 | | -const { hasOwn, keys: ObjectKeys, freeze, assign: ObjectAssign, create: ObjectCreate } = Object; |
41 | | - |
42 | | -// Diagnostic in form passed by user to `Context#report()` |
43 | | -export type Diagnostic = DiagnosticWithNode | DiagnosticWithLoc; |
44 | | - |
45 | | -export interface DiagnosticBase { |
46 | | - message?: string | null | undefined; |
47 | | - messageId?: string | null | undefined; |
48 | | - data?: Record<string, string | number> | null | undefined; |
49 | | - fix?: FixFn; |
50 | | -} |
51 | | - |
52 | | -export interface DiagnosticWithNode extends DiagnosticBase { |
53 | | - node: Ranged; |
54 | | -} |
55 | | - |
56 | | -export interface DiagnosticWithLoc extends DiagnosticBase { |
57 | | - loc: Location; |
58 | | -} |
59 | | - |
60 | | -// Diagnostic in form sent to Rust |
61 | | -interface DiagnosticReport { |
62 | | - message: string; |
63 | | - start: number; |
64 | | - end: number; |
65 | | - ruleIndex: number; |
66 | | - fixes: Fix[] | null; |
67 | | -} |
68 | | - |
69 | | -// Diagnostics array. Reused for every file. |
70 | | -export const diagnostics: DiagnosticReport[] = []; |
| 38 | +const { freeze, assign: ObjectAssign, create: ObjectCreate } = Object; |
71 | 39 |
|
72 | 40 | // Cached current working directory |
73 | 41 | let cwd: string | null = null; |
74 | 42 |
|
75 | 43 | // Absolute path of file being linted. |
76 | 44 | // When `null`, indicates that no file is currently being linted (in `createOnce`, or between linting files). |
77 | | -let filePath: string | null = null; |
| 45 | +export let filePath: string | null = null; |
78 | 46 |
|
79 | 47 | /** |
80 | 48 | * Set up context for linting a file. |
@@ -391,124 +359,8 @@ export function createContext(fullRuleName: string, ruleDetails: RuleDetails): R |
391 | 359 | * @throws {TypeError} If `diagnostic` is invalid |
392 | 360 | */ |
393 | 361 | report(diagnostic: Diagnostic): void { |
394 | | - // Delegate to `reportImpl`, passing rule-specific details (`RuleDetails`) |
395 | | - reportImpl(diagnostic, ruleDetails); |
| 362 | + // Delegate to `report` implementation shared between all rules, passing rule-specific details (`RuleDetails`) |
| 363 | + report(diagnostic, ruleDetails); |
396 | 364 | }, |
397 | 365 | } as unknown as Context); // It seems TS can't understand `__proto__: FILE_CONTEXT` |
398 | 366 | } |
399 | | - |
400 | | -/** |
401 | | - * Report error. |
402 | | - * @param diagnostic - Diagnostic object |
403 | | - * @param ruleDetails - `RuleDetails` object, containing rule-specific details e.g. `isFixable` |
404 | | - * @throws {TypeError} If `diagnostic` is invalid |
405 | | - */ |
406 | | -function reportImpl(diagnostic: Diagnostic, ruleDetails: RuleDetails): void { |
407 | | - if (filePath === null) throw new Error('Cannot report errors in `createOnce`'); |
408 | | - |
409 | | - // Get message, resolving message from `messageId` if present |
410 | | - let message = getMessage(diagnostic, ruleDetails); |
411 | | - |
412 | | - // Interpolate placeholders {{key}} with data values |
413 | | - if (hasOwn(diagnostic, 'data')) { |
414 | | - const { data } = diagnostic; |
415 | | - if (data != null) { |
416 | | - message = message.replace(/\{\{([^}]+)\}\}/g, (match, key) => { |
417 | | - key = key.trim(); |
418 | | - const value = data[key]; |
419 | | - return value !== undefined ? String(value) : match; |
420 | | - }); |
421 | | - } |
422 | | - } |
423 | | - |
424 | | - // TODO: Validate `diagnostic` |
425 | | - let start: number, end: number, loc: Location; |
426 | | - |
427 | | - if (hasOwn(diagnostic, 'loc') && (loc = (diagnostic as DiagnosticWithLoc).loc) != null) { |
428 | | - // `loc` |
429 | | - if (typeof loc !== 'object') throw new TypeError('`loc` must be an object'); |
430 | | - start = getOffsetFromLineColumn(loc.start); |
431 | | - end = getOffsetFromLineColumn(loc.end); |
432 | | - } else { |
433 | | - // `node` |
434 | | - const { node } = diagnostic as DiagnosticWithNode; |
435 | | - if (node == null) throw new TypeError('Either `node` or `loc` is required'); |
436 | | - if (typeof node !== 'object') throw new TypeError('`node` must be an object'); |
437 | | - |
438 | | - // ESLint uses `loc` here instead of `range`. |
439 | | - // We can't do that because AST nodes don't have `loc` property yet. In any case, `range` is preferable, |
440 | | - // as otherwise we have to convert `loc` to `range` which is expensive at present. |
441 | | - // TODO: Revisit this once we have `loc` support in AST, and a fast translation table to convert `loc` to `range`. |
442 | | - const { range } = node; |
443 | | - if (range === null || typeof range !== 'object') throw new TypeError('`node.range` must be present'); |
444 | | - start = range[0]; |
445 | | - end = range[1]; |
446 | | - |
447 | | - // Do type validation checks here, to ensure no error in serialization / deserialization. |
448 | | - // Range validation happens on Rust side. |
449 | | - if ( |
450 | | - typeof start !== 'number' || |
451 | | - typeof end !== 'number' || |
452 | | - start < 0 || |
453 | | - end < 0 || |
454 | | - (start | 0) !== start || |
455 | | - (end | 0) !== end |
456 | | - ) { |
457 | | - throw new TypeError('`node.range[0]` and `node.range[1]` must be non-negative integers'); |
458 | | - } |
459 | | - } |
460 | | - |
461 | | - diagnostics.push({ |
462 | | - message, |
463 | | - start, |
464 | | - end, |
465 | | - ruleIndex: ruleDetails.ruleIndex, |
466 | | - fixes: getFixes(diagnostic, ruleDetails), |
467 | | - }); |
468 | | -} |
469 | | - |
470 | | -/** |
471 | | - * Get message from diagnostic. |
472 | | - * @param diagnostic - Diagnostic object |
473 | | - * @param ruleDetails - `RuleDetails` object, containing rule-specific `messages` |
474 | | - * @returns Message string |
475 | | - * @throws {Error|TypeError} If neither `message` nor `messageId` provided, or of wrong type |
476 | | - */ |
477 | | -function getMessage(diagnostic: Diagnostic, ruleDetails: RuleDetails): string { |
478 | | - if (hasOwn(diagnostic, 'messageId')) { |
479 | | - const { messageId } = diagnostic as { messageId: string | null | undefined }; |
480 | | - if (messageId != null) return resolveMessageFromMessageId(messageId, ruleDetails); |
481 | | - } |
482 | | - |
483 | | - if (hasOwn(diagnostic, 'message')) { |
484 | | - const { message } = diagnostic; |
485 | | - if (typeof message === 'string') return message; |
486 | | - if (message != null) throw new TypeError('`message` must be a string'); |
487 | | - } |
488 | | - |
489 | | - throw new Error('Either `message` or `messageId` is required'); |
490 | | -} |
491 | | - |
492 | | -/** |
493 | | - * Resolve a message ID to its message string, with optional data interpolation. |
494 | | - * @param messageId - The message ID to resolve |
495 | | - * @param ruleDetails - `RuleDetails` object, containing rule-specific `messages` |
496 | | - * @returns Resolved message string |
497 | | - * @throws {Error} If `messageId` is not found in `messages` |
498 | | - */ |
499 | | -function resolveMessageFromMessageId(messageId: string, ruleDetails: RuleDetails): string { |
500 | | - const { messages } = ruleDetails; |
501 | | - if (messages === null) { |
502 | | - throw new Error(`Cannot use messageId '${messageId}' - rule does not define any messages in \`meta.messages\``); |
503 | | - } |
504 | | - |
505 | | - if (!hasOwn(messages, messageId)) { |
506 | | - throw new Error( |
507 | | - `Unknown messageId '${messageId}'. Available \`messageIds\`: ${ObjectKeys(messages) |
508 | | - .map((msg) => `'${msg}'`) |
509 | | - .join(', ')}`, |
510 | | - ); |
511 | | - } |
512 | | - |
513 | | - return messages[messageId]; |
514 | | -} |
0 commit comments