/
hint-context.ts
154 lines (130 loc) · 4.9 KB
/
hint-context.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
/**
* @fileoverview HintContext utility for hints
*
* Based on ESLint's rule-context
* https://github.com/eslint/eslint/blob/master/lib/hint-context.js
*/
import { URL } from 'url';
import { Engine } from './engine';
import {
Events,
HintMetadata,
HTMLElement,
NetworkData,
ProblemLocation,
Severity,
StringKeyOf
} from './types';
import { Category } from './enums/category';
export type ReportOptions = {
/** The source code to display (defaults to the `outerHTML` of `element`). */
codeSnippet?: string;
/** The text within `element` where the issue was found (used to refine a `ProblemLocation`). */
content?: string;
/** The `HTMLElement` where the issue was found (used to get a `ProblemLocation`). */
element?: HTMLElement | null;
/**
* The `ProblemLocation` where the issue was found.
* If specified with `element`, represents an offset in the element's content (e.g. for inline CSS in HTML).
*/
location?: ProblemLocation | null;
/** The `Severity` to report the issue as (overrides default settings for a hint). */
severity?: Severity;
};
/** Acts as an abstraction layer between hints and the main hint object. */
export class HintContext<E extends Events = Events> {
private id: string
private options: any[]
private meta: HintMetadata
private severity: Severity
private engine: Engine<E>
private ignoredUrls: RegExp[]
public constructor(hintId: string, engine: Engine<E>, severity: Severity, options: any, meta: HintMetadata, ignoredUrls: RegExp[]) {
this.id = hintId;
this.options = options;
this.meta = meta;
this.engine = engine;
this.severity = severity;
this.ignoredUrls = ignoredUrls;
Object.freeze(this);
}
/** The DOM of the page. */
public get pageDOM() {
return this.engine.pageDOM;
}
/** The original HTML of the page. */
public get pageContent() {
return this.engine.pageContent;
}
/** The headers of the response when retrieving the HTML. */
public get pageHeaders() {
return this.engine.pageHeaders;
}
/** List of browsers to target as specified by the hint configuration. */
public get targetedBrowsers(): string[] {
return this.engine.targetedBrowsers;
}
/** Custom configuration (if any) for the given hint */
public get hintOptions() {
if (Array.isArray(this.options)) {
return this.options[1];
}
return null;
}
/*
* ------------------------------------------------------------------------------
* Public methods
* ------------------------------------------------------------------------------
*/
/** Injects JavaScript into the target. */
public evaluate(source: string): Promise<any> {
return this.engine.evaluate(source);
}
/** A useful way of making requests. */
public fetchContent(target: string | URL, headers?: object): Promise<NetworkData> {
return this.engine.fetchContent(target, headers);
}
public querySelectorAll(selector: string): HTMLElement[] {
return this.engine.querySelectorAll(selector);
}
/** Finds the approximative location in the page's HTML for a match in an element. */
public findProblemLocation(element: HTMLElement, offset: ProblemLocation | null): ProblemLocation | null {
if (offset) {
return element.getContentLocation(offset);
}
return element.getLocation();
}
/** Reports a problem with the resource. */
public report(resource: string, message: string, options: ReportOptions = {}) {
const { codeSnippet, element, severity } = options;
let position = options.location || null;
let sourceCode: string | null = null;
if (element) {
// When element is provided, position is an offset in the content.
position = this.findProblemLocation(element, position);
sourceCode = element.outerHTML.replace(/[\t]/g, ' ');
}
/*
* If location is undefined or equal to null, `position` will be set as `{ column: -1, line: -1 }` later in `hint.report`.
* So pass the `location` on as it is.
*/
this.engine.report(
this.id,
(this.meta && this.meta.docs && this.meta.docs.category) ? this.meta.docs.category : Category.other,
severity || this.severity,
codeSnippet || sourceCode || '',
position,
message,
resource
);
}
/** Subscribe an event in hint. */
public on<K extends StringKeyOf<E>>(event: K, listener: (data: E[K], event: string) => void) {
this.engine.onHintEvent(this.id, event, listener);
}
public isUrlIgnored(resource: string) {
return this.ignoredUrls.some((urlIgnored: RegExp) => {
return urlIgnored.test(resource);
});
}
}