UNPKG

html-validate

Version:

Offline HTML5 validator and linter

464 lines (458 loc) 15.6 kB
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