/
DiagnosticReporter.ts
162 lines (138 loc) · 6.07 KB
/
DiagnosticReporter.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module Diagnostic
*/
import { Localization } from "@itwin/core-common";
import { AnyDiagnostic } from "./Diagnostic";
import assert = require("assert");
const translationNamespace = "ECSchemaMetaData";
const subTranslationNamespace = "Diagnostics";
const baseTranslationKey = `${translationNamespace}:${subTranslationNamespace}`;
/**
* Interface used to report [[IDiagnostic]] objects created during schema validation.
* @beta
*/
export interface IDiagnosticReporter {
/**
* A map where the key is a schema full name and the value is a collection
* of diagnostics codes identifying which rules violations to ignore during validation.
*/
suppressions?: Map<string, string[]>;
/** The localization object to use for message translation. */
localization?: Localization;
/**
* Handles the given [[IDiagnostic]] based on the implementation requirements for a
* given reporter.
* @param diagnostic The diagnostic to report.
*/
report(diagnostic: AnyDiagnostic): void;
}
/**
* An abstract base class for [[IDiagnosticReporter]] implementation that used the
* provided Map to suppress certain rule violations from being reported. The Map's key
* a schema full name, and the Map's value is a collection of rule codes to suppress.
* @beta
*/
export abstract class SuppressionDiagnosticReporter implements IDiagnosticReporter {
private _suppressions?: Map<string, string[]>;
/**
* Initializes a new SuppressionDiagnosticReporter
* @param suppressions A Map where the key is a schema full name and the value is collection of diagnostic codes to suppress.
*/
constructor(suppressions?: Map<string, string[]>) {
this._suppressions = suppressions;
}
/**
* Gets the collection of ISchemaDiagnosticSuppression objects that identify
* rules violations to ignore during validation.
*/
public get suppressions(): Map<string, string[]> | undefined {
return this._suppressions;
}
/**
* Prior to reporting the [[IDiagnostic]], the diagnostic message is formatted (with translations)
* base on arguments contained in the diagnostic. Calls reportDiagnostic after the message is formatted.
* @param diagnostic The diagnostic to report.
*/
public report(diagnostic: AnyDiagnostic) {
if (this._suppressions && this._suppressions.has(diagnostic.schema.fullName)) {
const suppressedCodes = this._suppressions.get(diagnostic.schema.fullName);
if (suppressedCodes!.includes(diagnostic.code))
return;
}
this.reportInternal(diagnostic);
}
/**
* Handles the given [[IDiagnostic]] based on the implementation requirements for a
* given reporter.
* @param diagnostic The diagnostic to report.
*/
protected abstract reportInternal(diagnostic: AnyDiagnostic): void;
}
/**
* An abstract [[SuppressionDiagnosticReporter]] implementation that formats the
* diagnostic message with the message args. If a Localization implementation is specified,
* the message will also be translated.
* @beta
*/
export abstract class FormatDiagnosticReporter extends SuppressionDiagnosticReporter {
/**
* Initializes a new FormatDiagnosticReporter
* @param suppressions A Map where the key is a schema full name and the value is collection of diagnostic codes to suppress.
* @param localization The Localization instance to use to translate validation messages.
*/
constructor(suppressions?: Map<string, string[]>, localization?: Localization) {
super(suppressions);
this.localization = localization;
}
/** The Localization object to use for message translation. If undefined, no translation will occur. */
public localization?: Localization;
/**
* Prior to reporting the [[IDiagnostic]], the diagnostic message is formatted (with translations)
* base on arguments contained in the diagnostic. Calls reportDiagnostic after the message is formatted.
* @param diagnostic The diagnostic to report.
*/
public reportInternal(diagnostic: AnyDiagnostic) {
const message = this.formatMessage(diagnostic);
this.reportDiagnostic(diagnostic, message);
}
/**
* Handles the given [[IDiagnostic]] based on the implementation requirements for a
* given reporter.
* @param diagnostic The diagnostic to report.
* @param messageText The formatted message.
*/
protected abstract reportDiagnostic(diagnostic: AnyDiagnostic, messageText: string): void;
/**
* Helper method that formats string with provided arguments where the place holders
* are in the the format '{0}', '{1}', etc.
* @param text The text to format.
* @param args The arguments to place in the text.
* @param baseIndex The base index for the args, used for validation (typically 0, which is the default).
*/
protected formatStringFromArgs(text: string, args: ArrayLike<string>, baseIndex = 0): string {
return text.replace(/{(\d+)}/g, (_match, index: string) => this.assertDefined(args[+index + baseIndex]));
}
private formatMessage(diagnostic: AnyDiagnostic): string {
let translatedMessage = this.translateMessage(diagnostic);
if (diagnostic.messageArgs && diagnostic.messageArgs.length > 0)
translatedMessage = this.formatStringFromArgs(translatedMessage, diagnostic.messageArgs);
return translatedMessage;
}
private translateMessage(diagnostic: AnyDiagnostic): string {
if (!this.localization)
return diagnostic.messageText;
return this.localization.getLocalizedString(this.getTranslationKey(diagnostic));
}
private getTranslationKey(diagnostic: AnyDiagnostic): string {
return `${baseTranslationKey}.${diagnostic.code}`;
}
private assertDefined<T>(value: T | null | undefined, message?: string): T {
if (value === undefined || value === null)
return assert(false, message) as never;
return value;
}
}