UNPKG

ace-linters

Version:

Ace linters is lsp client for Ace editor. It comes with large number of preconfigured easy to use in browser servers.

1,989 lines (1,675 loc) 1.5 MB
(function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define([], factory); else { var a = factory(); for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i]; } })(this, () => { return /******/ (() => { // webpackBootstrap /******/ var __webpack_modules__ = ({ /***/ 1542: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const { buildAst } = __webpack_require__(7523); const { accept } = __webpack_require__(24); const { DEFAULT_NS } = __webpack_require__(8319); module.exports = { buildAst: buildAst, accept: accept, DEFAULT_NS: DEFAULT_NS, }; /***/ }), /***/ 7523: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const { BaseXmlCstVisitor } = __webpack_require__(7882); const { last, forEach, reduce, map, pick, sortBy, isEmpty, isArray, assign, } = __webpack_require__(5250); const { findNextTextualToken, isXMLNamespaceKey, getXMLNamespaceKeyPrefix, } = __webpack_require__(1875); const { getAstChildrenReflective } = __webpack_require__(8831); const { DEFAULT_NS } = __webpack_require__(8319); /** * @param {DocumentCstNode} docCst * @param {IToken[]} tokenVector * @returns {XMLDocument} */ function buildAst(docCst, tokenVector) { AstBuilder.setState({ tokenVector }); const xmlDocAst = AstBuilder.visit(docCst); if (xmlDocAst.rootElement !== invalidSyntax) { updateNamespaces(xmlDocAst.rootElement); } return xmlDocAst; } /* eslint-disable no-unused-vars -- consistent signatures in visitor methods even if they are empty placeholders */ class CstToAstVisitor extends BaseXmlCstVisitor { constructor() { super(); } setState({ tokenVector }) { this.tokenVector = tokenVector; } visit(cstNode, params = {}) { return super.visit(cstNode, { location: cstNode.location, ...params }); } /** * @param ctx {DocumentCtx} * @param opts {Object} * @param opts.location {SourcePosition} * * @returns {XMLDocument} */ document(ctx, { location }) { const astNode = { type: "XMLDocument", rootElement: invalidSyntax, position: location, }; if (ctx.prolog !== undefined) { astNode.prolog = this.visit(ctx.prolog[0]); } if ( ctx.element !== undefined && isEmpty(ctx.element[0].children) === false ) { astNode.rootElement = this.visit(ctx.element[0]); } setChildrenParent(astNode); return astNode; } /** * @param ctx {PrologCtx} * @param opts {Object} * @param opts.location {SourcePosition} */ prolog(ctx, { location }) { const astNode = { type: "XMLProlog", attributes: [], position: location, }; if (ctx.attribute !== undefined) { astNode.attributes = map(ctx.attribute, (_) => this.visit(_, { isPrologParent: true }) ); } setChildrenParent(astNode); return astNode; } /** * @param {docTypeDeclCtx} ctx */ /* istanbul ignore next - place holder*/ docTypeDecl(ctx, astNode) {} /** * @param {ExternalIDCtx} ctx */ /* istanbul ignore next - place holder*/ externalID(ctx, astNode) {} /** * @param ctx {ContentCtx} * @param opts {Object} * @param opts.location {SourcePosition} * * @return {{elements, textContents}} */ content(ctx, { location }) { let elements = []; let textContents = []; if (ctx.element !== undefined) { elements = map(ctx.element, this.visit.bind(this)); } if (ctx.chardata !== undefined) { textContents = map(ctx.chardata, this.visit.bind(this)); } return { elements, textContents }; } /** * @param ctx {ElementCtx} * @param opts {Object} * @param opts.location {SourcePosition} */ element(ctx, { location }) { const astNode = { type: "XMLElement", // Avoid Accidental Keys in this map namespaces: Object.create(null), name: invalidSyntax, attributes: [], subElements: [], textContents: [], position: location, syntax: {}, }; if (ctx.attribute !== undefined) { astNode.attributes = map(ctx.attribute, this.visit.bind(this)); } if (ctx.content !== undefined) { const { elements, textContents } = this.visit(ctx.content[0]); astNode.subElements = elements; astNode.textContents = textContents; } handleElementOpenCloseNameRanges(astNode, ctx); handleElementOpenCloseBodyRanges(astNode, ctx); handleElementAttributeRanges(astNode, ctx, this.tokenVector); setChildrenParent(astNode); return astNode; } /** * @param ctx {ReferenceCtx} * @param opts {Object} * @param opts.location {SourcePosition} */ /* istanbul ignore next - place holder*/ reference(ctx, { location }) { // Irrelevant for the AST at this time } /** * @param ctx {AttributeCtx} * @param opts {Object} * @param opts.location {SourcePosition} * @param opts.isPrologParent {boolean} */ attribute(ctx, { location, isPrologParent }) { const astNode = { type: isPrologParent ? "XMLPrologAttribute" : "XMLAttribute", position: location, key: invalidSyntax, value: invalidSyntax, syntax: {}, }; /* istanbul ignore else - Defensive Coding, not actually possible else branch */ if (ctx.Name !== undefined && ctx.Name[0].isInsertedInRecovery !== true) { const keyToken = ctx.Name[0]; astNode.key = keyToken.image; astNode.syntax.key = toXMLToken(keyToken); } if ( ctx.STRING !== undefined && ctx.STRING[0].isInsertedInRecovery !== true ) { const valueToken = ctx.STRING[0]; astNode.value = stripQuotes(valueToken.image); astNode.syntax.value = toXMLToken(valueToken); } setChildrenParent(astNode); return astNode; } /** * @param ctx {ChardataCtx} * @param opts {Object} * @param opts.location {SourcePosition} */ chardata(ctx, { location }) { const astNode = { type: "XMLTextContent", position: location, text: invalidSyntax, }; let allTokens = []; if (ctx.SEA_WS !== undefined) { allTokens = allTokens.concat(ctx.SEA_WS); } if (ctx.TEXT !== undefined) { allTokens = allTokens.concat(ctx.TEXT); } const sortedTokens = sortBy(allTokens, ["startOffset"]); const fullText = map(sortedTokens, "image").join(""); astNode.text = fullText; return astNode; } /** * @param ctx {MiscCtx} * @param opts {Object} * @param opts.location {SourcePosition} */ /* istanbul ignore next - place holder*/ misc(ctx, { location }) { // Irrelevant for the AST at this time } } /* eslint-enable no-unused-vars -- see matching disable comment */ const AstBuilder = new CstToAstVisitor(); function setChildrenParent(astParent) { const astChildren = getAstChildrenReflective(astParent); forEach(astChildren, (child) => (child.parent = astParent)); } /** * @param {XMLElement} element * @param {Record<Prefix, Uri>} prevNamespaces */ function updateNamespaces(element, prevNamespaces = []) { const currElemNamespaces = reduce( element.attributes, (result, attrib) => { /* istanbul ignore else - Defensive Coding, not actually possible branch */ if (attrib.key !== invalidSyntax) { if ( isXMLNamespaceKey({ key: attrib.key, includeEmptyPrefix: false }) === true ) { const prefix = getXMLNamespaceKeyPrefix(attrib.key); // TODO: Support un-defining namespaces (including the default one) if (attrib.value) { const uri = attrib.value; if (prefix !== "") { result[prefix] = uri; } else { // default namespace result[DEFAULT_NS] = uri; } } } } return result; }, {} ); const emptyMap = Object.create(null); // "newer" (closer scope) namespaces definitions will overwrite "older" ones. element.namespaces = assign(emptyMap, prevNamespaces, currElemNamespaces); forEach(element.subElements, (subElem) => updateNamespaces(subElem, element.namespaces) ); } /** * @param {chevrotain.IToken} token */ function toXMLToken(token) { return pick(token, [ "image", "startOffset", "endOffset", "startLine", "endLine", "startColumn", "endColumn", ]); } function startOfXMLToken(token) { return pick(token, ["startOffset", "startLine", "startColumn"]); } function endOfXMLToken(token) { return pick(token, ["endOffset", "endLine", "endColumn"]); } function exists(tokArr) { return ( isArray(tokArr) && tokArr.length === 1 && tokArr[0].isInsertedInRecovery !== true ); } function stripQuotes(quotedText) { return quotedText.substring(1, quotedText.length - 1); } /** * @param {string} text */ function nsToParts(text) { const matchResult = /^([^:]+):([^:]+)$/.exec(text); if (matchResult === null) { return null; } const ns = matchResult[1]; const name = matchResult[2]; return { ns, name }; } /** * @type {InvalidSyntax} */ const invalidSyntax = null; /** * @param {XMLElement} astNode * @param {ElementCtx} ctx */ function handleElementOpenCloseNameRanges(astNode, ctx) { if (ctx.Name !== undefined && ctx.Name[0].isInsertedInRecovery !== true) { const openNameToken = ctx.Name[0]; astNode.syntax.openName = toXMLToken(openNameToken); const nsParts = nsToParts(openNameToken.image); if (nsParts !== null) { astNode.ns = nsParts.ns; astNode.name = nsParts.name; } else { astNode.name = openNameToken.image; } } if ( ctx.END_NAME !== undefined && ctx.END_NAME[0].isInsertedInRecovery !== true ) { astNode.syntax.closeName = toXMLToken(ctx.END_NAME[0]); } } /** * @param {XMLElement} astNode * @param {ElementCtx} ctx */ function handleElementOpenCloseBodyRanges(astNode, ctx) { /* istanbul ignore else - Defensive Coding */ if (exists(ctx.OPEN)) { let openBodyCloseTok = undefined; /* istanbul ignore else - Defensive Coding */ if (exists(ctx.START_CLOSE)) { openBodyCloseTok = ctx.START_CLOSE[0]; astNode.syntax.isSelfClosing = false; } else if (exists(ctx.SLASH_CLOSE)) { openBodyCloseTok = ctx.SLASH_CLOSE[0]; astNode.syntax.isSelfClosing = true; } if (openBodyCloseTok !== undefined) { astNode.syntax.openBody = { ...startOfXMLToken(ctx.OPEN[0]), ...endOfXMLToken(openBodyCloseTok), }; } if (exists(ctx.SLASH_OPEN) && exists(ctx.END)) { astNode.syntax.closeBody = { ...startOfXMLToken(ctx.SLASH_OPEN[0]), ...endOfXMLToken(ctx.END[0]), }; } } } /** * @param {XMLElement} astNode * @param {ElementCtx} ctx * @param {IToken[]} tokenVector */ function handleElementAttributeRanges(astNode, ctx, tokenVector) { if (exists(ctx.Name)) { const startOffset = ctx.Name[0].endOffset + 2; // Valid `attributesRange` exists if (exists(ctx.START_CLOSE) || exists(ctx.SLASH_CLOSE)) { const endOffset = (exists(ctx.START_CLOSE) ? ctx.START_CLOSE[0].startOffset : ctx.SLASH_CLOSE[0].startOffset) - 1; astNode.syntax.attributesRange = { startOffset, endOffset }; } // Have to scan-ahead and guess where the attributes range ends else { const hasAttributes = isArray(ctx.attribute); const lastKnownAttribRangeTokenEnd = hasAttributes ? last(ctx.attribute).location.endOffset : ctx.Name[0].endOffset; const nextTextualToken = findNextTextualToken( tokenVector, lastKnownAttribRangeTokenEnd ); if (nextTextualToken !== null) { astNode.syntax.guessedAttributesRange = { startOffset, endOffset: nextTextualToken.endOffset - 1, }; } } } } module.exports = { buildAst: buildAst, }; /***/ }), /***/ 8319: /***/ ((module) => { module.exports = { DEFAULT_NS: "::DEFAULT", }; /***/ }), /***/ 8831: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const { reduce, has, isArray } = __webpack_require__(5250); function getAstChildrenReflective(astParent) { const astChildren = reduce( astParent, (result, prop, name) => { if (name === "parent") { // parent property is never a child... } else if (has(prop, "type")) { result.push(prop); } else if (isArray(prop) && prop.length > 0 && has(prop[0], "type")) { result = result.concat(prop); } return result; }, [] ); return astChildren; } module.exports = { getAstChildrenReflective: getAstChildrenReflective, }; /***/ }), /***/ 24: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const { forEach, isFunction } = __webpack_require__(5250); const { getAstChildrenReflective } = __webpack_require__(8831); /** * @param {XMLAstNode} node * @param {XMLAstVisitor} visitor * * @returns {void} */ function accept(node, visitor) { switch (node.type) { case "XMLDocument": { if (isFunction(visitor.visitXMLDocument)) { visitor.visitXMLDocument(node); } break; } case "XMLProlog": { if (isFunction(visitor.visitXMLProlog)) { visitor.visitXMLProlog(node); } break; } case "XMLPrologAttribute": { if (isFunction(visitor.visitXMLPrologAttribute)) { visitor.visitXMLPrologAttribute(node); } break; } case "XMLElement": { if (isFunction(visitor.visitXMLElement)) { visitor.visitXMLElement(node); } break; } case "XMLAttribute": { if (isFunction(visitor.visitXMLAttribute)) { visitor.visitXMLAttribute(node); } break; } case "XMLTextContent": { if (isFunction(visitor.visitXMLTextContent)) { visitor.visitXMLTextContent(node); } break; } /* istanbul ignore next defensive programming */ default: throw Error("None Exhaustive Match"); } const astChildren = getAstChildrenReflective(node); forEach(astChildren, (childNode) => { accept(childNode, visitor); }); } module.exports = { accept: accept, }; /***/ }), /***/ 1875: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const { findNextTextualToken } = __webpack_require__(2266); const { isXMLNamespaceKey, getXMLNamespaceKeyPrefix, } = __webpack_require__(2370); module.exports = { findNextTextualToken: findNextTextualToken, isXMLNamespaceKey: isXMLNamespaceKey, getXMLNamespaceKeyPrefix: getXMLNamespaceKeyPrefix, }; /***/ }), /***/ 2266: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const { findIndex } = __webpack_require__(5250); function findNextTextualToken(tokenVector, prevTokenEndOffset) { // The TokenVector is sorted, so we could use a BinarySearch to optimize performance const prevTokenIdx = findIndex( tokenVector, (tok) => tok.endOffset === prevTokenEndOffset ); let nextTokenIdx = prevTokenIdx; let found = false; while (found === false) { nextTokenIdx++; const nextPossibleToken = tokenVector[nextTokenIdx]; // No Next textualToken if (nextPossibleToken === undefined) { return null; } /* istanbul ignore next * I don't think this scenario can be created, however defensive coding never killed anyone... * Basically SEA_WS can only only appear in "OUTSIDE" mode, and we need a CLOSE/SLASH_CLOSE to get back to outside * mode, however if we had those this function would never have been called... */ if (nextPossibleToken.tokenType.name === "SEA_WS") { // skip pure WS tokens as they do not contain any actual text } else { return nextPossibleToken; } } } module.exports = { findNextTextualToken: findNextTextualToken, }; /***/ }), /***/ 2370: /***/ ((module) => { // The xml parser takes care of validating the attribute name. // If the user started the attribute name with "xmlns:" we can assume that // they meant for it to be an xml namespace attribute. // xmlns attributes explicitly can't contain ":" after the "xmlns:" part. const namespaceRegex = /^xmlns(?<prefixWithColon>:(?<prefix>[^:]*))?$/; /** * See comment in api.d.ts. * * @param {string} key * @param {boolean} includeEmptyPrefix * @returns {boolean} */ function isXMLNamespaceKey({ key, includeEmptyPrefix }) { if (typeof key !== "string") { return false; } const matchArr = key.match(namespaceRegex); // No match - this is not an xmlns key if (matchArr === null) { return false; } return !!( includeEmptyPrefix === true || // "xmlns" case !matchArr.groups.prefixWithColon || // "xmlns:<prefix>" case matchArr.groups.prefix ); } /** * See comment in api.d.ts. * * @param {string} key * @returns {string|undefined} */ function getXMLNamespaceKeyPrefix(key) { if (typeof key !== "string") { return undefined; } const matchArr = key.match(namespaceRegex); if (matchArr === null) { return undefined; } return (matchArr.groups && matchArr.groups.prefix) || ""; } module.exports = { isXMLNamespaceKey: isXMLNamespaceKey, getXMLNamespaceKeyPrefix: getXMLNamespaceKeyPrefix, }; /***/ }), /***/ 30: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const { validate } = __webpack_require__(8737); const { validateUniqueAttributeKeys, } = __webpack_require__(4232); const { validateTagClosingNameMatch, } = __webpack_require__(8403); /** * @param {XMLDocument} ast * @returns {ValidationIssue[]} */ function checkConstraints(ast) { const constraintIssues = validate({ doc: ast, validators: { element: [validateTagClosingNameMatch, validateUniqueAttributeKeys], }, }); return constraintIssues; } module.exports = { checkConstraints, }; /***/ }), /***/ 8403: /***/ ((module) => { /** * @param {XMLElement} elem * @returns {ValidationIssue[]} */ function validateTagClosingNameMatch(elem) { const openTagToken = elem.syntax.openName; const closeTagToken = elem.syntax.closeName; // The element tag must have **both** the opening and closing tokens // to be able to validate. if (!openTagToken || !closeTagToken) { return []; } // alles gut if (openTagToken.image === closeTagToken.image) { return []; } else { return [ { msg: `tags mismatch: "${openTagToken.image}" must match closing tag: "${closeTagToken.image}"`, node: elem, severity: "error", position: { startOffset: openTagToken.startOffset, endOffset: openTagToken.endOffset, }, }, { msg: `tags mismatch: "${closeTagToken.image}" must match opening tag: "${openTagToken.image}"`, node: elem, severity: "error", position: { startOffset: closeTagToken.startOffset, endOffset: closeTagToken.endOffset, }, }, ]; } } module.exports = { validateTagClosingNameMatch, }; /***/ }), /***/ 4232: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const { groupBy, pickBy, reduce, map, filter } = __webpack_require__(5250); /** * @param {XMLElement} elem * @returns {ValidationIssue[]} */ function validateUniqueAttributeKeys(elem) { const attributesWithKeys = filter(elem.attributes, (_) => _.key !== null); const attribByKey = groupBy(attributesWithKeys, "key"); const nonUniqueAttribsGroups = pickBy(attribByKey, (_) => _.length > 1); const nonUniqueAttribs = reduce( nonUniqueAttribsGroups, (result, attribsGroup) => result.concat(attribsGroup), [] ); const validationIssues = map(nonUniqueAttribs, (_) => { // the `key` is guaranteed to exist because we have filtered above // for attributes with valid keys const keyToken = _.syntax.key; return { msg: `duplicate attribute: "${_.key}"`, node: _, severity: "error", position: { startOffset: keyToken.startOffset, endOffset: keyToken.endOffset, }, }; }); return validationIssues; } module.exports = { validateUniqueAttributeKeys, }; /***/ }), /***/ 7882: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const { xmlLexer } = __webpack_require__(3799); const { xmlParser } = __webpack_require__(8322); module.exports = { parse: function parse(text) { const lexResult = xmlLexer.tokenize(text); // setting a new input will RESET the parser instance's state. xmlParser.input = lexResult.tokens; // any top level rule may be used as an entry point const cst = xmlParser.document(); return { cst: cst, tokenVector: lexResult.tokens, lexErrors: lexResult.errors, parseErrors: xmlParser.errors, }; }, BaseXmlCstVisitor: xmlParser.getBaseCstVisitorConstructor(), }; /***/ }), /***/ 3799: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const { createToken: createTokenOrg, Lexer } = __webpack_require__(397); // A little mini DSL for easier lexer definition. const fragments = {}; const f = fragments; function FRAGMENT(name, def) { fragments[name] = typeof def === "string" ? def : def.source; } function makePattern(strings, ...args) { let combined = ""; for (let i = 0; i < strings.length; i++) { combined += strings[i]; if (i < args.length) { let pattern = args[i]; // By wrapping in a RegExp (none) capturing group // We enabled the safe usage of qualifiers and assertions. combined += `(?:${pattern})`; } } return new RegExp(combined); } const tokensArray = []; const tokensDictionary = {}; function createToken(options) { const newTokenType = createTokenOrg(options); tokensArray.push(newTokenType); tokensDictionary[options.name] = newTokenType; return newTokenType; } FRAGMENT( "NameStartChar", "(:|[a-zA-Z]|_|\\u2070-\\u218F|\\u2C00-\\u2FEF|\\u3001-\\uD7FF|\\uF900-\\uFDCF|\\uFDF0-\\uFFFD)" ); FRAGMENT( "NameChar", makePattern`${f.NameStartChar}|-|\\.|\\d|\\u00B7||[\\u0300-\\u036F]|[\\u203F-\\u2040]` ); FRAGMENT("Name", makePattern`${f.NameStartChar}(${f.NameChar})*`); const Comment = createToken({ name: "Comment", pattern: /<!--(.|\r?\n)*?-->/, // A Comment may span multiple lines. line_breaks: true, }); const CData = createToken({ name: "CData", pattern: /<!\[CDATA\[(.|\r?\n)*?]]>/, line_breaks: true, }); const DocType = createToken({ name: "DocType", pattern: /<!DOCTYPE/, push_mode: "INSIDE", }); const IgnoredDTD = createToken({ name: "DTD", pattern: /<!.*?>/, group: Lexer.SKIPPED, }); const EntityRef = createToken({ name: "EntityRef", pattern: makePattern`&${f.Name};`, }); const CharRef = createToken({ name: "CharRef", pattern: /&#\d+;|&#x[a-fA-F0-9]/, }); const SEA_WS = createToken({ name: "SEA_WS", pattern: /( |\t|\n|\r\n)+/, }); const XMLDeclOpen = createToken({ name: "XMLDeclOpen", pattern: /<\?xml[ \t\r\n]/, push_mode: "INSIDE", }); const SLASH_OPEN = createToken({ name: "SLASH_OPEN", pattern: /<\//, push_mode: "INSIDE", }); const INVALID_SLASH_OPEN = createToken({ name: "INVALID_SLASH_OPEN", pattern: /<\//, categories: [SLASH_OPEN], }); const PROCESSING_INSTRUCTION = createToken({ name: "PROCESSING_INSTRUCTION", pattern: makePattern`<\\?${f.Name}.*\\?>`, }); const OPEN = createToken({ name: "OPEN", pattern: /</, push_mode: "INSIDE" }); // Meant to avoid skipping '<' token in a partial sequence of elements. // Example of the problem this solves: // < // <from>john</from> // - The second '<' will be skipped because in the mode "INSIDE" '<' is not recognized. // - This means the AST will include only a single element instead of two const INVALID_OPEN_INSIDE = createToken({ name: "INVALID_OPEN_INSIDE", pattern: /</, categories: [OPEN], }); const TEXT = createToken({ name: "TEXT", pattern: /[^<&]+/ }); const CLOSE = createToken({ name: "CLOSE", pattern: />/, pop_mode: true }); const SPECIAL_CLOSE = createToken({ name: "SPECIAL_CLOSE", pattern: /\?>/, pop_mode: true, }); const SLASH_CLOSE = createToken({ name: "SLASH_CLOSE", pattern: /\/>/, pop_mode: true, }); const SLASH = createToken({ name: "SLASH", pattern: /\// }); const STRING = createToken({ name: "STRING", pattern: /"[^<"]*"|'[^<']*'/, }); const EQUALS = createToken({ name: "EQUALS", pattern: /=/ }); const Name = createToken({ name: "Name", pattern: makePattern`${f.Name}` }); const S = createToken({ name: "S", pattern: /[ \t\r\n]/, group: Lexer.SKIPPED, }); const xmlLexerDefinition = { defaultMode: "OUTSIDE", modes: { OUTSIDE: [ Comment, CData, DocType, IgnoredDTD, EntityRef, CharRef, SEA_WS, XMLDeclOpen, SLASH_OPEN, PROCESSING_INSTRUCTION, OPEN, TEXT, ], INSIDE: [ // Tokens from `OUTSIDE` to improve error recovery behavior Comment, INVALID_SLASH_OPEN, INVALID_OPEN_INSIDE, // "Real" `INSIDE` tokens CLOSE, SPECIAL_CLOSE, SLASH_CLOSE, SLASH, EQUALS, STRING, Name, S, ], }, }; const xmlLexer = new Lexer(xmlLexerDefinition, { // Reducing the amount of position tracking can provide a small performance boost (<10%) // Likely best to keep the full info for better error position reporting and // to expose "fuller" ITokens from the Lexer. positionTracking: "full", ensureOptimizations: false, // TODO: inspect definitions for XML line terminators lineTerminatorCharacters: ["\n"], lineTerminatorsPattern: /\n|\r\n/g, }); module.exports = { xmlLexer, tokensDictionary, }; /***/ }), /***/ 8322: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const { CstParser, tokenMatcher } = __webpack_require__(397); const { tokensDictionary: t } = __webpack_require__(3799); class Parser extends CstParser { constructor() { super(t, { maxLookahead: 1, recoveryEnabled: true, nodeLocationTracking: "full", }); this.deletionRecoveryEnabled = true; const $ = this; $.RULE("document", () => { $.OPTION(() => { $.SUBRULE($.prolog); }); $.MANY(() => { $.SUBRULE($.misc); }); $.OPTION2(() => { $.SUBRULE($.docTypeDecl); }); $.MANY2(() => { $.SUBRULE2($.misc); }); $.SUBRULE($.element); $.MANY3(() => { $.SUBRULE3($.misc); }); }); $.RULE("prolog", () => { $.CONSUME(t.XMLDeclOpen); $.MANY(() => { $.SUBRULE($.attribute); }); $.CONSUME(t.SPECIAL_CLOSE); }); // https://www.w3.org/TR/xml/#NT-doctypedecl $.RULE("docTypeDecl", () => { $.CONSUME(t.DocType); $.CONSUME(t.Name); $.OPTION(() => { $.SUBRULE($.externalID); }); // The internal subSet part is intentionally not implemented because we do not at this // time wish to implement a full DTD Parser as part of this project... // https://www.w3.org/TR/xml/#NT-intSubset $.CONSUME(t.CLOSE); }); $.RULE("externalID", () => { // Using gates to assert the value of the "Name" Identifiers. // We could use Categories to model un-reserved keywords, however I am not sure // The added complexity is needed at this time... $.OR([ { GATE: () => $.LA(1).image === "SYSTEM", ALT: () => { $.CONSUME2(t.Name, { LABEL: "System" }); $.CONSUME(t.STRING, { LABEL: "SystemLiteral" }); }, }, { GATE: () => $.LA(1).image === "PUBLIC", ALT: () => { $.CONSUME3(t.Name, { LABEL: "Public" }); $.CONSUME2(t.STRING, { LABEL: "PubIDLiteral" }); $.CONSUME3(t.STRING, { LABEL: "SystemLiteral" }); }, }, ]); }); $.RULE("content", () => { $.MANY(() => { $.OR([ { ALT: () => $.SUBRULE($.element) }, { ALT: () => $.SUBRULE($.chardata) }, { ALT: () => $.SUBRULE($.reference) }, { ALT: () => $.CONSUME(t.CData) }, { ALT: () => $.CONSUME(t.PROCESSING_INSTRUCTION) }, { ALT: () => $.CONSUME(t.Comment) }, ]); }); }); $.RULE("element", () => { $.CONSUME(t.OPEN); try { this.deletionRecoveryEnabled = false; // disabling single token deletion here // because `< // </note>` // will be parsed as: `<note>` // and the next element will be lost $.CONSUME(t.Name); } finally { this.deletionRecoveryEnabled = true; } $.MANY(() => { $.SUBRULE($.attribute); }); $.OR([ { ALT: () => { $.CONSUME(t.CLOSE, { LABEL: "START_CLOSE" }); $.SUBRULE($.content); $.CONSUME(t.SLASH_OPEN); $.CONSUME2(t.Name, { LABEL: "END_NAME" }); $.CONSUME2(t.CLOSE, { LABEL: "END" }); }, }, { ALT: () => { $.CONSUME(t.SLASH_CLOSE); }, }, ]); }); $.RULE("reference", () => { $.OR([ { ALT: () => $.CONSUME(t.EntityRef) }, { ALT: () => $.CONSUME(t.CharRef) }, ]); }); $.RULE("attribute", () => { $.CONSUME(t.Name); try { this.deletionRecoveryEnabled = false; // disabling single token deletion here // because `attrib1 attrib2="666` // will be parsed as: `attrib1="666` $.CONSUME(t.EQUALS); // disabling single token deletion here // to avoid new elementName being $.CONSUME(t.STRING); } finally { this.deletionRecoveryEnabled = true; } }); $.RULE("chardata", () => { $.OR([ { ALT: () => $.CONSUME(t.TEXT) }, { ALT: () => $.CONSUME(t.SEA_WS) }, ]); }); $.RULE("misc", () => { $.OR([ { ALT: () => $.CONSUME(t.Comment) }, { ALT: () => $.CONSUME(t.PROCESSING_INSTRUCTION) }, { ALT: () => $.CONSUME(t.SEA_WS) }, ]); }); this.performSelfAnalysis(); } canRecoverWithSingleTokenDeletion(expectedTokType) { if (this.deletionRecoveryEnabled === false) { return false; } return super.canRecoverWithSingleTokenDeletion(expectedTokType); } // TODO: provide this fix upstream to chevrotain // https://github.com/SAP/chevrotain/issues/1055 /* istanbul ignore next - should be tested as part of Chevrotain */ findReSyncTokenType() { const allPossibleReSyncTokTypes = this.flattenFollowSet(); // this loop will always terminate as EOF is always in the follow stack and also always (virtually) in the input let nextToken = this.LA(1); let k = 2; /* eslint-disable-next-line no-constant-condition -- see above comment */ while (true) { const foundMatch = allPossibleReSyncTokTypes.find((resyncTokType) => { const canMatch = tokenMatcher(nextToken, resyncTokType); return canMatch; }); if (foundMatch !== undefined) { return foundMatch; } nextToken = this.LA(k); k++; } } } // Re-use the same parser instance const xmlParser = new Parser(); module.exports = { xmlParser, }; /***/ }), /***/ 7728: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const { getSchemaValidators } = __webpack_require__(2258); const { getSchemaSuggestionsProviders } = __webpack_require__(4638); module.exports = { getSchemaValidators: getSchemaValidators, getSchemaSuggestionsProviders: getSchemaSuggestionsProviders, }; /***/ }), /***/ 1851: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const { difference, map } = __webpack_require__(5250); /** * @param {XMLElement} elementNode * @param {XSSElement} xssElement * * @returns {CompletionSuggestion[]} */ function attributeNameCompletion(elementNode, xssElement) { const possibleSuggestions = map(xssElement.attributes, (_) => _.key); const existingAttribNames = map(elementNode.attributes, (_) => _.key); const possibleNewSuggestions = difference( possibleSuggestions, existingAttribNames ); const suggestions = map(possibleNewSuggestions, (_) => { return { text: _, label: _, commitCharacter: "=", }; }); return suggestions; } module.exports = { attributeNameCompletion: attributeNameCompletion, }; /***/ }), /***/ 2650: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const { has, isRegExp, forEach, isArray } = __webpack_require__(5250); /** * @param {XMLAttribute} attributeNode * @param {XSSAttribute} xssAttribute * @param {string} prefix * * @returns {CompletionSuggestion[]} */ function attributeValueCompletion(attributeNode, xssAttribute, prefix = "") { // An XSS Attribute definition may not specify any constraints on a value if (has(xssAttribute, "value") === false) { return []; } const suggestions = []; const valueDef = xssAttribute.value; /* istanbul ignore else - defensive programming */ if (isRegExp(valueDef)) { // No suggestions for regExp value definitions... } else if (isArray(valueDef)) { forEach(valueDef, (enumVal) => { if (enumVal.startsWith(prefix)) { suggestions.push({ text: enumVal.substring(prefix.length), label: enumVal, }); } }); } else { /* istanbul ignore next defensive programming */ throw Error("None Exhaustive Match"); } return suggestions; } module.exports = { attributeValueCompletion: attributeValueCompletion, }; /***/ }), /***/ 2548: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const { difference, map, filter, has, pickBy } = __webpack_require__(5250); const { DEFAULT_NS } = __webpack_require__(1542); // https://www.w3.org/TR/2009/REC-xml-names-20091208/#NT-PrefixedName const NAMESPACE_PATTERN = /^(?:([^:]*):)?([^:]*)$/; /** * * Note that the Element (XML/XSS) are of the parent node of the element * in which content assist was requested. * * @param {XMLElement} elementNode * @param {XSSElement} xssElement * * @returns {CompletionSuggestion[]} */ function elementNameCompletion(elementNode, xssElement, prefix = "") { const match = prefix.match(NAMESPACE_PATTERN); if (match === null) { return []; } // If there is no prefix, use the default namespace prefix const namespacePrefix = match[1] ? match[1] : DEFAULT_NS; const elementNamespaceUri = elementNode.namespaces[namespacePrefix]; const possibleElements = filter( xssElement.elements, (_) => has(_, "namespace") === false || (_.namespace && _.namespace === elementNamespaceUri) ); const possibleSuggestionsWithoutExistingSingular = applicableElements( xssElement.elements, elementNode.subElements, possibleElements ); const suggestions = map(possibleSuggestionsWithoutExistingSingular, (_) => { return { text: _, label: _, }; }); if (namespacePrefix === undefined || namespacePrefix === DEFAULT_NS) { // Can't really suggest anything for the `implicit` default namespace... const namespacesWithoutDefault = pickBy( elementNode.namespaces, (uri, prefix) => prefix !== DEFAULT_NS ); const applicableNamespaces = pickBy(namespacesWithoutDefault, (uri) => { const possibleElements = filter( xssElement.elements, (element) => has(element, "namespace") === true && element.namespace === uri ); const possibleSuggestionsWithoutExistingSingular = applicableElements( xssElement.elements, elementNode.subElements, possibleElements ); const namespaceHasApplicableElements = possibleSuggestionsWithoutExistingSingular.length > 0; return namespaceHasApplicableElements; }); const namespaceSuggestions = map(applicableNamespaces, (uri, prefix) => ({ text: prefix, label: prefix, commitCharacter: ":", isNamespace: true, })); return [...namespaceSuggestions, ...suggestions]; } return suggestions; } function applicableElements(xssElements, subElements, possibleElements) { const allPossibleSuggestions = map( possibleElements, (element) => element.name ); const notSingularElem = filter( xssElements, (element) => element.cardinality === "many" ); const notSingularElemNames = map(notSingularElem, (element) => element.name); const existingElemNames = map(subElements, (element) => element.name); const existingSingular = difference(existingElemNames, notSingularElemNames); const possibleSuggestionsWithoutExistingSingular = difference( allPossibleSuggestions, existingSingular ); return possibleSuggestionsWithoutExistingSingular; } module.exports = { elementNameCompletion: elementNameCompletion, }; /***/ }), /***/ 4638: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const { attributeNameCompletion } = __webpack_require__(1851); const { attributeValueCompletion, } = __webpack_require__(2650); const { elementNameCompletion } = __webpack_require__(2548); const { findElementXssDef, findAttributeXssDef } = __webpack_require__(8317); function getSchemaSuggestionsProviders(schema) { const attributeNameProvider = buildAttributeNameProvider(schema); const attributeValueProvider = buildAttributeValueProvider(schema); const elementNameProvider = buildElementNameProvider(schema); return { schemaElementNameCompletion: elementNameProvider, schemaAttributeNameCompletion: attributeNameProvider, schemaAttributeValueCompletion: attributeValueProvider, }; } /** * @param {SimpleSchema} schema */ function buildAttributeNameProvider(schema) { return ({ element, prefix }) => { const xssElementDef = findElementXssDef(element, schema); if (xssElementDef !== undefined) { return attributeNameCompletion(element, xssElementDef, prefix); } else { return []; } }; } /** * @param {SimpleSchema} schema */ function buildElementNameProvider(schema) { return ({ element, prefix }) => { // Note we are finding the definition for the element's parent // Because the information on possible sibling elements exists there... const xssElementDef = findElementXssDef(element.parent, schema); if (xssElementDef !== undefined) { return elementNameCompletion(element.parent, xssElementDef, prefix); } else { return []; } }; } /** * @param {SimpleSchema} schema */ function buildAttributeValueProvider(schema) { return ({ attribute, prefix }) => { const attributeXssDef = findAttributeXssDef(attribute, schema); return attributeValueCompletion(attribute, attributeXssDef, prefix); }; } module.exports = { getSchemaSuggestionsProviders: getSchemaSuggestionsProviders, }; /***/ }), /***/ 2258: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const { validateAttributeValue } = __webpack_require__(7578); const { validateDuplicateSubElements, } = __webpack_require__(1688); const { validateRequiredAttributes, } = __webpack_require__(5587); const { validateRequiredSubElements, } = __webpack_require__(6762); const { validateUnknownAttributes, } = __webpack_require__(6336); const { validateUnknownSubElements, } = __webpack_require__(1573); const { findAttributeXssDef, findElementXssDef } = __webpack_require__(8317); function getSchemaValidators(schema) { const attributeValidator = buildAttributeValidator(schema); const elementValidator = buildElementValidator(schema); return { attribute: attributeValidator, element: elementValidator, }; } /** * @param {SimpleSchema} schema */ function buildAttributeValidator(schema) { return (attributeNode) => { let issues = []; const xssAttributeDef = findAttributeXssDef(attributeNode, schema); if (xssAttributeDef !== undefined) { const attributeValueIssues = validateAttributeValue( attributeNode, xssAttributeDef ); issues = issues.concat(attributeValueIssues); } return issues; }; } /** * @param {SimpleSchema} schema */ function buildElementValidator(schema) { return (elementNode) => { let issues = []; const xssElementDef = findElementXssDef(elementNode, schema); if (xssElementDef !== undefined) { const duplicateElementsIssues = validateDuplicateSubElements( elementNode, xssElementDef ); const requiredAttributesIssues = validateRequiredAttributes( elementNode, xssElementDef ); const requiredSubElementsIssues = validateRequiredSubElements( elementNode, xssElementDef ); const unknownAttributesIssues = validateUnknownAttributes( elementNode, xssElementDef ); const unknownSubElementsIssues = validateUnknownSubElements( elementNode, xssElementDef ); issues = issues.concat( duplicateElementsIssues, requiredAttributesIssues, requiredSubElementsIssues, unknownAttributesIssues, unknownSubElementsIssues ); } return issues; }; } module.exports = { getSchemaValidators: getSchemaValidators, }; /***/ }), /***/ 8317: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const { drop, map, forEach, first } = __webpack_require__(5250); /** * @param {XMLAttribute} attribNode * @param {SimpleSchema} schema */ function findAttributeXssDef(attribNode, schema) { const xssElement = findElementXssDef(attribNode.parent, schema); let xssAttribute = undefined; if (xssElement !== undefined) { const attributeName = attribNode.key; xssAttribute = xssElement.attributes[attributeName]; } return xssAttribute; } /** * @param {XMLElement} node * @param {SimpleSchema} schema */ function findElementXssDef(node, schema) { const ancestors = getAstNodeAncestors(node); const elementsPath = map(ancestors, "name"); const rootElement = first(elementsPath); // Root Element mis-match, The Schema cannot provide any attribute validations for this XML AST. if (rootElement !== schema.name) { return undefined; } let xssElement = schema; forEach(drop(elementsPath), (elemName) => { // traverse subElements xssElement = xssElement.elements[elemName]; if (xssElement === undefined) { return false; } }); return xssElement; } /** * @param {XMLAstNode} node * * @returns {XMLAstNode[]} - The Ancestors do not include the XMLDocument */ function getAstNodeAncestors(node) { const ancestors = []; ancestors.push(node); let currAncestor = node.parent; while ( currAncestor !== undefined && // The Simple Schema only starts at the root Element (not the Root Document). currAncestor.type !== "XMLDocument" ) { ancestors.push(currAncestor); currAncestor = currAncestor.parent; } ancestors.reverse(); return ancestors; } module.exports = { findAttributeXssDef: findAttributeXssDef, findElementXssDef: findElementXssDef, }; /***/ }), /***/ 7578: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const { isRegExp, isArray, includes, has } = __webpack_require__(5250); const { tokenToOffsetPosition } = __webpack_require__(4663); /** * @param {XMLAttribute} attributeNode * @param {XSSAttribute }xssAttribute * * @returns {ValidationIssue[]} */ function validateAttributeValue(attributeNode, xssAttribute) { const issues = []; const valueDef = xssAttribute.value; // An XSS Attribute definition may not specify any constraints on a value if (has(xssAttribute, "value") === false) { return issues; } const actualValue = attributeNode.value; if (actualValue === null) { // we cannot validate a partial attribute AST without an actual value... return issues; } // This is always safe because at this point we know the attribute has a value const errPosition = tokenToOffsetPosition(attributeNode.syntax.value); /* istanbul ignore else defensive programming */ if (isRegExp(valueDef)) { if (valueDef.test(actualValue) === false) { issues.push({ msg: `Expecting Value matching <${valueDef.toString()}> but found <${actualValue}>`, node: attributeNode, severity: "error", position: errPosition, }); } } else if (isArray(valueDef)) { if (includes(valueDef, actualValue) === false) { issues.push({ msg: `Expecting one of <${valueDef.toString()}> but found <${actualValue}>`, node: attributeNode, severity: "error", position: errPosition, }); } } else { /* istanbul ignore next defensive programming */ throw Error("None Exhaustive Match"); } return issues; } module.exports = { validateAttributeValue: validateAttributeValue, }; /***/ }), /***/ 1688: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const { map, forEach, includes, filter, groupBy } = __webpack_require__(5250); const { tokenToOffsetPosition } = __webpack_require__(4663); /** * @param {XMLElement} elem * @param {XSSElement} schema * * @returns {ValidationIssue[]} */ function validateDuplicateSubElements(elem, schema) { const allowedDupElem = filter( schema.elements, (_) => _.cardinality === "many" ); const allowedDupElemNames = map(allowedDupElem, (_) => _.name); const actualSubElemByName = groupBy(elem.subElements, (_) => _.name); const issues = []; forEach(actualSubElemByName, (dupElements, dupElementsName) => { const allowedDup = includes(allowedDupElemNames, dupElementsName); const hasConfiguration = schema.elements[dupElementsName] !== undefined; const hasDuplicates = dupElements.length > 1; if (allowedDup === false && hasDuplicates && hasConfiguration) { forEach(dupElements, (dupElem) => { issues.push({ msg: `Duplicate Sub-Element: <${dupElem.name}> only a single occurrence of this Sub-Element is allowed here.`, node: dupElem, severity: "error", // safe assumption that we have an `openName` (see above condition) position: tokenToOffsetPosition(dupElem.syntax.openName), }); }); } }); return issues; } module.exports = { validateDuplicateSubElements: validateDuplicateSubElements, }; /***/ }), /***/ 5587: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const { map, filter, difference } = __webpack_require__(5250); const { tokenToOffsetPosition } = __webpack_require__(4663); /** * @param {XMLElement} elem * @param {XSSElement} schema * * @returns {ValidationIssue[]} */ function validateRequiredAttributes(elem, schema) { const requiredAttribsDef = filter( schema.attributes, (_) => _.required === true ); const requiredAttribNames = map(requiredAttribsDef, (_) => _.key); const actualAttribNames = map(elem.attributes, (_) => _.key); const missingAttributesNames = difference( requiredAttribNames, actualAttribNames ); // This elementName must always exist, otherwise we could not locate the relevant schema definition // so this validation could have never executed... const errPosition = tokenToOffsetPosition(elem.syntax.openName); const issues = map(missingAttributesNames, (_) => { return { msg: `Missing Required Attribute: <${_}>`, node: elem, severity: "error", position: errPosition, }; }); return issues; } module.exports = { validateRequiredAttributes: validateRequiredAttributes, }; /***/ }), /***/ 6762: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const { map, filter, difference } = __webpack_require__(5250); const { tokenToOffsetPosition } = __webpack_require__(4663); /** * @param {XMLElement} elem * @param {XSSElement} schema * * @returns {ValidationIssue[]} */ function validateRequiredSubElements(elem, schema) { const requiredSubElemsDef = filter( schema.elements, (_) => _.required === true ); const requiredElemNames = map(requiredSubElemsDef, (_) => _.name); const actualSubElemNameNames = map(elem.subElements, (_) => _.name); const missingSubElemNames = difference( requiredElemNames, actualSubElemNameNames ); // This elementName must always exist, otherwise we could not locate the relevant schema definition // so this validation could have never executed... const errPosition = tokenToOffsetPosition(elem.syntax.openName); const issues = map(missingSubElemNames, (_) => { return { msg: `Missing Required Sub-Element: <${_}>`, node: elem, severity: "error", position: errPosition, }; }); return issues; } module.exports = { validateRequiredSubElements: validateRequiredSubElements, }; /***/ }), /***/ 6336: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const { map, includes, forEach } = __webpack_require__(5250); const { isXMLNamespaceKey } = __webpack_require__(1875); const { tokenToOffsetPosition } = __webpack_require__(4663); /** * @param {XMLElement} elem * @param {XSSElement} schema * * @returns {ValidationIssue[]} */ function validateUnknownAttributes(elem, schema) { // This validation is only relevant if the Schema disallows unknown attributes. if (schema.attributesType !== "closed") { return []; } const allowedAttribNames = map(schema.attributes, (_) => _.key); const issues = []; forEach(elem.attributes, (attrib) => { /* istanbul ignore else - Defensive progra