-
Notifications
You must be signed in to change notification settings - Fork 23
Expand file tree
/
Copy pathanalyzer.js
More file actions
195 lines (173 loc) · 6.02 KB
/
analyzer.js
File metadata and controls
195 lines (173 loc) · 6.02 KB
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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
/**
* Core DOM-agnostic analyzer for capo.js
* Provides single-pass analysis of HTML <head> elements
*
* @module analyzer
*/
import * as rules from './lib/rules.js';
import { getValidationWarnings, getCustomValidations } from './lib/validation.js';
/**
* @typedef {Object} AnalysisResult
* @property {Array<WeightInfo>} weights - Weight information for each element
* @property {Array<ValidationWarning>} validationWarnings - Document-level validation warnings
* @property {Array<CustomValidation>} customValidations - Element-level validation warnings
* @property {any} headElement - Reference to the analyzed head element
*/
/**
* @typedef {Object} WeightInfo
* @property {any} element - The DOM/AST element
* @property {number} weight - Computed weight (0-10)
* @property {string} category - Weight category name (META, TITLE, etc.)
*/
/**
* @typedef {Object} ValidationWarning
* @property {string} warning - Warning message
* @property {any|Array<any>} [element] - Associated element(s)
* @property {Array<any>} [elements] - Associated elements array
*/
/**
* @typedef {Object} CustomValidation
* @property {any} element - The element with validation issues
* @property {Array<string>} warnings - Validation warning messages
*/
/**
* Analyze the head element and return element weights, validation warnings, and custom validations.
*
* @param {any} headNode - The head element to analyze
* @param {Object} adapter - Adapter for element operations
* @param {Object} [options={}] - Analysis options
* @param {boolean} [options.includeValidation=true] - Whether to include document-level validation warnings
* @param {boolean} [options.includeCustomValidations=true] - Whether to include element-level custom validations
* @returns {Object} Analysis results
* @returns {Array} returns.weights - Array of element weight objects
* @returns {Array} returns.validationWarnings - Document-level validation warnings
* @returns {Array} returns.customValidations - Element-level custom validation results
* @returns {any} returns.headElement - The analyzed head element
*
* @example
* const adapter = new HtmlEslintAdapter();
* const results = analyzeHead(head, adapter);
*
* console.log(`Found ${results.weights.length} elements`);
* console.log(`${results.validationWarnings.length} document warnings`);
*/
export function analyzeHead(headNode, adapter, options = {}) {
const {
includeValidation = true,
includeCustomValidations = true,
} = options;
// Pass 1: Compute weights for all elements
const weights = rules.getHeadWeights(headNode, adapter);
// Pass 2: Get document-level validation warnings
const validationWarnings = includeValidation
? getValidationWarnings(headNode, adapter)
: [];
// Pass 3: Get element-level custom validations
const customValidations = includeCustomValidations
? getElementValidations(headNode, adapter)
: [];
return {
weights,
validationWarnings,
customValidations,
headElement: headNode,
};
}
/**
* Get custom validations for all elements in head
*
* @param {any} headNode - The <head> element
* @param {Object} adapter - HTMLAdapter implementation
* @returns {Array<CustomValidation>}
* @private
*/
function getElementValidations(headNode, adapter) {
const customValidations = [];
const children = adapter.getChildren(headNode);
for (const element of children) {
const validation = getCustomValidations(element, adapter, headNode);
if (validation && validation.warnings && validation.warnings.length > 0) {
customValidations.push({
ruleId: validation.ruleId,
element,
warnings: validation.warnings,
payload: validation.payload,
});
}
}
return customValidations;
}
/**
* Get weight category name from weight value
*
* @param {number} weight - Weight value (0-10)
* @returns {string} Category name
*
* @example
* getWeightCategory(10); // 'META'
* getWeightCategory(9); // 'TITLE'
* getWeightCategory(0); // 'OTHER'
*/
export function getWeightCategory(weight) {
// Find the category that matches this weight
for (const [category, value] of Object.entries(rules.ElementWeights)) {
if (value === weight) {
return category;
}
}
return 'UNKNOWN';
}
/**
* Check if elements are in optimal order
*
* @param {Array<WeightInfo>} weights - Weight information array
* @returns {Array<Object>} Array of ordering violations
*
* @example
* const weights = analyzeHead(head, adapter).weights;
* const violations = checkOrdering(weights);
* console.log(`${violations.length} ordering issues found`);
*/
export function checkOrdering(weights) {
const violations = [];
for (let i = 0; i < weights.length - 1; i++) {
const current = weights[i];
const next = weights[i + 1];
if (current.weight < next.weight) {
const currentCategory = getWeightCategory(current.weight);
const nextCategory = getWeightCategory(next.weight);
violations.push({
index: i + 1,
currentElement: current.element,
nextElement: next.element,
currentWeight: current.weight,
nextWeight: next.weight,
currentCategory,
nextCategory,
message: `${nextCategory} element should come before ${currentCategory} element`,
});
}
}
return violations;
}
/**
* Analyze and return both weights and ordering violations
* Convenience function that combines analyzeHead() and checkOrdering()
*
* @param {any} headNode - The <head> element
* @param {Object} adapter - HTMLAdapter implementation
* @param {Object} [options={}] - Analysis options
* @returns {Object} Combined analysis with weights, violations, and validations
*
* @example
* const analysis = analyzeHeadWithOrdering(head, adapter);
* console.log(`${analysis.orderingViolations.length} ordering issues`);
*/
export function analyzeHeadWithOrdering(headNode, adapter, options = {}) {
const result = analyzeHead(headNode, adapter, options);
const orderingViolations = checkOrdering(result.weights);
return {
...result,
orderingViolations,
};
}