html-validate
Version:
Offline HTML5 validator and linter
464 lines (458 loc) • 15.6 kB
JavaScript
import { f as StaticConfigLoader, K as normalizeSource, L as transformSource, O as Engine, P as Parser, Q as transformSourceSync, X as transformFilename, Y as transformFilenameSync, B as Reporter, Z as configurationSchema, _ as isThenable, U as UserError, b as ConfigLoader, $ as compatibilityCheckImpl, v as version } from './core.js';
function isSourceHooks(value) {
if (!value || typeof value === "string") {
return false;
}
return Boolean(value.processAttribute ?? value.processElement);
}
function isConfigData(value) {
if (!value || typeof value === "string") {
return false;
}
return !(value.processAttribute ?? value.processElement);
}
class HtmlValidate {
configLoader;
constructor(arg) {
const [loader, config] = arg instanceof ConfigLoader ? [arg, void 0] : [void 0, arg];
this.configLoader = loader ?? new StaticConfigLoader(config);
}
/* eslint-enable @typescript-eslint/unified-signatures */
validateString(str, arg1, arg2, arg3) {
const filename = typeof arg1 === "string" ? arg1 : "inline";
const options = isConfigData(arg1) ? arg1 : isConfigData(arg2) ? arg2 : void 0;
const hooks = isSourceHooks(arg1) ? arg1 : isSourceHooks(arg2) ? arg2 : arg3;
const source = {
data: str,
filename,
line: 1,
column: 1,
offset: 0,
hooks
};
return this.validateSource(source, options);
}
/* eslint-enable @typescript-eslint/unified-signatures */
validateStringSync(str, arg1, arg2, arg3) {
const filename = typeof arg1 === "string" ? arg1 : "inline";
const options = isConfigData(arg1) ? arg1 : isConfigData(arg2) ? arg2 : void 0;
const hooks = isSourceHooks(arg1) ? arg1 : isSourceHooks(arg2) ? arg2 : arg3;
const source = {
data: str,
filename,
line: 1,
column: 1,
offset: 0,
hooks
};
return this.validateSourceSync(source, options);
}
/**
* Parse and validate HTML from [[Source]].
*
* @public
* @param input - Source to parse.
* @returns Report output.
*/
async validateSource(input, configOverride) {
const source = normalizeSource(input);
const config = await this.getConfigFor(source.filename, configOverride);
const resolvers = this.configLoader.getResolvers();
const transformedSource = await transformSource(resolvers, config, source);
const engine = new Engine(config, Parser);
return engine.lint(transformedSource);
}
/**
* Parse and validate HTML from [[Source]].
*
* @public
* @param input - Source to parse.
* @returns Report output.
*/
validateSourceSync(input, configOverride) {
const source = normalizeSource(input);
const config = this.getConfigForSync(source.filename, configOverride);
const resolvers = this.configLoader.getResolvers();
const transformedSource = transformSourceSync(resolvers, config, source);
const engine = new Engine(config, Parser);
return engine.lint(transformedSource);
}
/**
* Parse and validate HTML from file.
*
* @public
* @param filename - Filename to read and parse.
* @returns Report output.
*/
async validateFile(filename, fs) {
const config = await this.getConfigFor(filename);
const resolvers = this.configLoader.getResolvers();
const source = await transformFilename(resolvers, config, filename, fs);
const engine = new Engine(config, Parser);
return Promise.resolve(engine.lint(source));
}
/**
* Parse and validate HTML from file.
*
* @public
* @param filename - Filename to read and parse.
* @returns Report output.
*/
validateFileSync(filename, fs) {
const config = this.getConfigForSync(filename);
const resolvers = this.configLoader.getResolvers();
const source = transformFilenameSync(resolvers, config, filename, fs);
const engine = new Engine(config, Parser);
return engine.lint(source);
}
/**
* Parse and validate HTML from multiple files. Result is merged together to a
* single report.
*
* @param filenames - Filenames to read and parse.
* @returns Report output.
*/
async validateMultipleFiles(filenames, fs) {
return Reporter.merge(filenames.map((filename) => this.validateFile(filename, fs)));
}
/**
* Parse and validate HTML from multiple files. Result is merged together to a
* single report.
*
* @param filenames - Filenames to read and parse.
* @returns Report output.
*/
validateMultipleFilesSync(filenames, fs) {
return Reporter.merge(filenames.map((filename) => this.validateFileSync(filename, fs)));
}
/**
* Returns true if the given filename can be validated.
*
* A file is considered to be validatable if the extension is `.html` or if a
* transformer matches the filename.
*
* This is mostly useful for tooling to determine whenever to validate the
* file or not. CLI tools will run on all the given files anyway.
*/
async canValidate(filename) {
if (filename.toLowerCase().endsWith(".html")) {
return true;
}
const config = await this.getConfigFor(filename);
return config.canTransform(filename);
}
/**
* Returns true if the given filename can be validated.
*
* A file is considered to be validatable if the extension is `.html` or if a
* transformer matches the filename.
*
* This is mostly useful for tooling to determine whenever to validate the
* file or not. CLI tools will run on all the given files anyway.
*/
canValidateSync(filename) {
if (filename.toLowerCase().endsWith(".html")) {
return true;
}
const config = this.getConfigForSync(filename);
return config.canTransform(filename);
}
/**
* Tokenize filename and output all tokens.
*
* Using CLI this is enabled with `--dump-tokens`. Mostly useful for
* debugging.
*
* @internal
* @param filename - Filename to tokenize.
*/
async dumpTokens(filename, fs) {
const config = await this.getConfigFor(filename);
const resolvers = this.configLoader.getResolvers();
const source = await transformFilename(resolvers, config, filename, fs);
const engine = new Engine(config, Parser);
return engine.dumpTokens(source);
}
/**
* Parse filename and output all events.
*
* Using CLI this is enabled with `--dump-events`. Mostly useful for
* debugging.
*
* @internal
* @param filename - Filename to dump events from.
*/
async dumpEvents(filename, fs) {
const config = await this.getConfigFor(filename);
const resolvers = this.configLoader.getResolvers();
const source = await transformFilename(resolvers, config, filename, fs);
const engine = new Engine(config, Parser);
return engine.dumpEvents(source);
}
/**
* Parse filename and output DOM tree.
*
* Using CLI this is enabled with `--dump-tree`. Mostly useful for
* debugging.
*
* @internal
* @param filename - Filename to dump DOM tree from.
*/
async dumpTree(filename, fs) {
const config = await this.getConfigFor(filename);
const resolvers = this.configLoader.getResolvers();
const source = await transformFilename(resolvers, config, filename, fs);
const engine = new Engine(config, Parser);
return engine.dumpTree(source);
}
/**
* Transform filename and output source data.
*
* Using CLI this is enabled with `--dump-source`. Mostly useful for
* debugging.
*
* @internal
* @param filename - Filename to dump source from.
*/
async dumpSource(filename, fs) {
const config = await this.getConfigFor(filename);
const resolvers = this.configLoader.getResolvers();
const sources = await transformFilename(resolvers, config, filename, fs);
return sources.reduce((result, source) => {
const line = String(source.line);
const column = String(source.column);
const offset = String(source.offset);
result.push(`Source ${source.filename}@${line}:${column} (offset: ${offset})`);
if (source.transformedBy) {
result.push("Transformed by:");
result = result.concat(source.transformedBy.reverse().map((name) => ` - ${name}`));
}
if (source.hooks && Object.keys(source.hooks).length > 0) {
result.push("Hooks");
for (const [key, present] of Object.entries(source.hooks)) {
if (present) {
result.push(` - ${key}`);
}
}
}
result.push("---");
result = result.concat(source.data.split("\n"));
result.push("---");
return result;
}, []);
}
/**
* Get effective configuration schema.
*/
getConfigurationSchema() {
return Promise.resolve(configurationSchema);
}
/**
* Get effective metadata element schema.
*
* If a filename is given the configured plugins can extend the
* schema. Filename must not be an existing file or a filetype normally
* handled by html-validate but the path will be used when resolving
* configuration. As a rule-of-thumb, set it to the elements json file.
*/
async getElementsSchema(filename) {
const config = await this.getConfigFor(filename ?? "inline");
const metaTable = config.getMetaTable();
return metaTable.getJSONSchema();
}
/**
* Get effective metadata element schema.
*
* If a filename is given the configured plugins can extend the
* schema. Filename must not be an existing file or a filetype normally
* handled by html-validate but the path will be used when resolving
* configuration. As a rule-of-thumb, set it to the elements json file.
*/
getElementsSchemaSync(filename) {
const config = this.getConfigForSync(filename ?? "inline");
const metaTable = config.getMetaTable();
return metaTable.getJSONSchema();
}
async getContextualDocumentation(message, filenameOrConfig = "inline") {
const config = typeof filenameOrConfig === "string" ? await this.getConfigFor(filenameOrConfig) : await filenameOrConfig;
const engine = new Engine(config, Parser);
return engine.getRuleDocumentation(message);
}
getContextualDocumentationSync(message, filenameOrConfig = "inline") {
const config = typeof filenameOrConfig === "string" ? this.getConfigForSync(filenameOrConfig) : filenameOrConfig;
const engine = new Engine(config, Parser);
return engine.getRuleDocumentation(message);
}
/**
* Get contextual documentation for the given rule.
*
* Typical usage:
*
* ```js
* const report = await htmlvalidate.validateFile("my-file.html");
* for (const result of report.results){
* const config = await htmlvalidate.getConfigFor(result.filePath);
* for (const message of result.messages){
* const documentation = await htmlvalidate.getRuleDocumentation(message.ruleId, config, message.context);
* // do something with documentation
* }
* }
* ```
*
* @public
* @deprecated Deprecated since 8.0.0, use [[getContextualDocumentation]] instead.
* @param ruleId - Rule to get documentation for.
* @param config - If set it provides more accurate description by using the
* correct configuration for the file.
* @param context - If set to `Message.context` some rules can provide
* contextual details and suggestions.
*/
async getRuleDocumentation(ruleId, config = null, context = null) {
const c = config ?? this.getConfigFor("inline");
const engine = new Engine(await c, Parser);
return engine.getRuleDocumentation({ ruleId, context });
}
/**
* Get contextual documentation for the given rule.
*
* Typical usage:
*
* ```js
* const report = htmlvalidate.validateFileSync("my-file.html");
* for (const result of report.results){
* const config = htmlvalidate.getConfigForSync(result.filePath);
* for (const message of result.messages){
* const documentation = htmlvalidate.getRuleDocumentationSync(message.ruleId, config, message.context);
* // do something with documentation
* }
* }
* ```
*
* @public
* @deprecated Deprecated since 8.0.0, use [[getContextualDocumentationSync]] instead.
* @param ruleId - Rule to get documentation for.
* @param config - If set it provides more accurate description by using the
* correct configuration for the file.
* @param context - If set to `Message.context` some rules can provide
* contextual details and suggestions.
*/
getRuleDocumentationSync(ruleId, config = null, context = null) {
const c = config ?? this.getConfigForSync("inline");
const engine = new Engine(c, Parser);
return engine.getRuleDocumentation({ ruleId, context });
}
/**
* Create a parser configured for given filename.
*
* @internal
* @param source - Source to use.
*/
async getParserFor(source) {
const config = await this.getConfigFor(source.filename);
return new Parser(config);
}
/**
* Get configuration for given filename.
*
* See [[FileSystemConfigLoader]] for details.
*
* @public
* @param filename - Filename to get configuration for.
* @param configOverride - Configuration to apply last.
*/
getConfigFor(filename, configOverride) {
const config = this.configLoader.getConfigFor(filename, configOverride);
return Promise.resolve(config);
}
/**
* Get configuration for given filename.
*
* See [[FileSystemConfigLoader]] for details.
*
* @public
* @param filename - Filename to get configuration for.
* @param configOverride - Configuration to apply last.
*/
getConfigForSync(filename, configOverride) {
const config = this.configLoader.getConfigFor(filename, configOverride);
if (isThenable(config)) {
throw new UserError("Cannot use asynchronous config loader with synchronous api");
}
return config;
}
/**
* Get current configuration loader.
*
* @public
* @since %version%
* @returns Current configuration loader.
*/
/* istanbul ignore next -- not testing setters/getters */
getConfigLoader() {
return this.configLoader;
}
/**
* Set configuration loader.
*
* @public
* @since %version%
* @param loader - New configuration loader to use.
*/
/* istanbul ignore next -- not testing setters/getters */
setConfigLoader(loader) {
this.configLoader = loader;
}
/**
* Flush configuration cache. Clears full cache unless a filename is given.
*
* See [[FileSystemConfigLoader]] for details.
*
* @public
* @param filename - If set, only flush cache for given filename.
*/
flushConfigCache(filename) {
this.configLoader.flushCache(filename);
}
}
function importFunction(id) {
return import(id);
}
async function internalImport(id) {
const { default: defaultImport } = await importFunction(id);
if (!defaultImport) {
throw new UserError(`"${id}" does not have a default export`);
}
return defaultImport;
}
function esmResolver() {
return {
name: "esm-resolver",
resolveElements(id) {
return internalImport(id);
},
resolveConfig(id) {
return internalImport(id);
},
resolvePlugin(id) {
return internalImport(id);
},
async resolveTransformer(id) {
return internalImport(id);
}
};
}
const defaults = {
silent: false,
version,
logger(text) {
console.error(text);
}
};
function compatibilityCheck(name, declared, options) {
return compatibilityCheckImpl(name, declared, {
...defaults,
...options
});
}
export { HtmlValidate as H, compatibilityCheck as c, esmResolver as e };
//# sourceMappingURL=core-browser.js.map