mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	When tracking tokens, we weren't handling the case of unterminated, single-char quote. Test cases are added to cover the fix. Original Revision: https://phabricator.services.mozilla.com/D214917 Differential Revision: https://phabricator.services.mozilla.com/D215069
		
			
				
	
	
		
			194 lines
		
	
	
	
		
			5.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			194 lines
		
	
	
	
		
			5.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
						|
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
 | 
						|
"use strict";
 | 
						|
 | 
						|
const EEOFCHARACTERS_NONE = 0x0000;
 | 
						|
 | 
						|
// to handle \<EOF> inside strings
 | 
						|
const EEOFCHARACTERS_DROPBACKSLASH = 0x0001;
 | 
						|
 | 
						|
// to handle \<EOF> outside strings
 | 
						|
const EEOFCHARACTERS_REPLACEMENTCHAR = 0x0002;
 | 
						|
 | 
						|
// to close comments
 | 
						|
const EEOFCHARACTERS_ASTERISK = 0x0004;
 | 
						|
const EEOFCHARACTERS_SLASH = 0x0008;
 | 
						|
 | 
						|
// to close double-quoted strings
 | 
						|
const EEOFCHARACTERS_DOUBLEQUOTE = 0x0010;
 | 
						|
 | 
						|
// to close single-quoted strings
 | 
						|
const EEOFCHARACTERS_SINGLEQUOTE = 0x0020;
 | 
						|
 | 
						|
// to close URLs
 | 
						|
const EEOFCHARACTERS_CLOSEPAREN = 0x0040;
 | 
						|
 | 
						|
// Bridge the char/string divide.
 | 
						|
const APOSTROPHE = "'".charCodeAt(0);
 | 
						|
const ASTERISK = "*".charCodeAt(0);
 | 
						|
const QUOTATION_MARK = '"'.charCodeAt(0);
 | 
						|
const RIGHT_PARENTHESIS = ")".charCodeAt(0);
 | 
						|
const SOLIDUS = "/".charCodeAt(0);
 | 
						|
 | 
						|
const UCS2_REPLACEMENT_CHAR = 0xfffd;
 | 
						|
 | 
						|
const kImpliedEOFCharacters = [
 | 
						|
  UCS2_REPLACEMENT_CHAR,
 | 
						|
  ASTERISK,
 | 
						|
  SOLIDUS,
 | 
						|
  QUOTATION_MARK,
 | 
						|
  APOSTROPHE,
 | 
						|
  RIGHT_PARENTHESIS,
 | 
						|
  0,
 | 
						|
];
 | 
						|
 | 
						|
/**
 | 
						|
 * Wrapper around InspectorCSSParser.
 | 
						|
 * Once/if https://github.com/servo/rust-cssparser/pull/374 lands, we can remove this class.
 | 
						|
 */
 | 
						|
class InspectorCSSParserWrapper {
 | 
						|
  #offset = 0;
 | 
						|
  #trackEOFChars;
 | 
						|
  #eofCharacters = EEOFCHARACTERS_NONE;
 | 
						|
 | 
						|
  /**
 | 
						|
   *
 | 
						|
   * @param {String} input: The CSS text to lex
 | 
						|
   * @param {Object} options
 | 
						|
   * @param {Boolean} options.trackEOFChars: Set to true if performEOFFixup will be called.
 | 
						|
   */
 | 
						|
  constructor(input, options = {}) {
 | 
						|
    this.parser = new InspectorCSSParser(input);
 | 
						|
    this.#trackEOFChars = options.trackEOFChars;
 | 
						|
  }
 | 
						|
 | 
						|
  get lineNumber() {
 | 
						|
    return this.parser.lineNumber;
 | 
						|
  }
 | 
						|
 | 
						|
  get columnNumber() {
 | 
						|
    return this.parser.columnNumber;
 | 
						|
  }
 | 
						|
 | 
						|
  nextToken() {
 | 
						|
    const token = this.parser.nextToken();
 | 
						|
    if (!token) {
 | 
						|
      return token;
 | 
						|
    }
 | 
						|
 | 
						|
    if (this.#trackEOFChars) {
 | 
						|
      const { tokenType, text } = token;
 | 
						|
      const lastChar = text[text.length - 1];
 | 
						|
      if (tokenType === "Comment" && lastChar !== `/`) {
 | 
						|
        if (lastChar === `*`) {
 | 
						|
          this.#eofCharacters = EEOFCHARACTERS_SLASH;
 | 
						|
        } else {
 | 
						|
          this.#eofCharacters = EEOFCHARACTERS_ASTERISK | EEOFCHARACTERS_SLASH;
 | 
						|
        }
 | 
						|
      } else if (tokenType === "QuotedString" || tokenType === "BadString") {
 | 
						|
        if (lastChar === "\\") {
 | 
						|
          this.#eofCharacters =
 | 
						|
            this.#eofCharacters | EEOFCHARACTERS_DROPBACKSLASH;
 | 
						|
        }
 | 
						|
        if (text[0] !== lastChar || text.length === 1) {
 | 
						|
          this.#eofCharacters =
 | 
						|
            this.#eofCharacters |
 | 
						|
            (text[0] === `"`
 | 
						|
              ? EEOFCHARACTERS_DOUBLEQUOTE
 | 
						|
              : EEOFCHARACTERS_SINGLEQUOTE);
 | 
						|
        }
 | 
						|
      } else {
 | 
						|
        if (lastChar === "\\") {
 | 
						|
          this.#eofCharacters = EEOFCHARACTERS_REPLACEMENTCHAR;
 | 
						|
        }
 | 
						|
 | 
						|
        // For some reason, we only automatically close `url`, other functions
 | 
						|
        // will have their opening parenthesis escaped.
 | 
						|
        if (
 | 
						|
          (tokenType === "Function" && token.value === "url") ||
 | 
						|
          tokenType === "BadUrl" ||
 | 
						|
          (tokenType === "UnquotedUrl" && lastChar !== ")")
 | 
						|
        ) {
 | 
						|
          this.#eofCharacters = this.#eofCharacters | EEOFCHARACTERS_CLOSEPAREN;
 | 
						|
        }
 | 
						|
 | 
						|
        if (tokenType === "CloseParenthesis") {
 | 
						|
          this.#eofCharacters =
 | 
						|
            this.#eofCharacters & ~EEOFCHARACTERS_CLOSEPAREN;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // At the moment, InspectorCSSParser doesn't expose offsets, so we need to compute
 | 
						|
    // them manually here.
 | 
						|
    // We can do that because we are retrieving every token in the input string, and so the
 | 
						|
    // end offset of the last token is the start offset of the new token.
 | 
						|
    token.startOffset = this.#offset;
 | 
						|
    this.#offset += token.text.length;
 | 
						|
    token.endOffset = this.#offset;
 | 
						|
    return token;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * When EOF is reached, the last token might be unterminated in some
 | 
						|
   * ways.  This method takes an input string and appends the needed
 | 
						|
   * terminators.  In particular:
 | 
						|
   *
 | 
						|
   * 1. If EOF occurs mid-string, this will append the correct quote.
 | 
						|
   * 2. If EOF occurs in a url token, this will append the close paren.
 | 
						|
   * 3. If EOF occurs in a comment this will append the comment closer.
 | 
						|
   *
 | 
						|
   * A trailing backslash might also have been present in the input
 | 
						|
   * string.  This is handled in different ways, depending on the
 | 
						|
   * context and arguments.
 | 
						|
   *
 | 
						|
   * The existing backslash at the end of inputString is preserved, and a new backslash
 | 
						|
   * is appended.
 | 
						|
   * That is, the input |\| is transformed to |\\|, and the
 | 
						|
   * input |'\| is transformed to |'\\'|.
 | 
						|
   *
 | 
						|
   * @param inputString the input string
 | 
						|
   * @return the input string with the termination characters appended
 | 
						|
   */
 | 
						|
  performEOFFixup(inputString) {
 | 
						|
    let result = inputString;
 | 
						|
 | 
						|
    let eofChars = this.#eofCharacters;
 | 
						|
    if (
 | 
						|
      (eofChars &
 | 
						|
        (EEOFCHARACTERS_DROPBACKSLASH | EEOFCHARACTERS_REPLACEMENTCHAR)) !=
 | 
						|
      0
 | 
						|
    ) {
 | 
						|
      eofChars &= ~(
 | 
						|
        EEOFCHARACTERS_DROPBACKSLASH | EEOFCHARACTERS_REPLACEMENTCHAR
 | 
						|
      );
 | 
						|
      result += "\\";
 | 
						|
    }
 | 
						|
 | 
						|
    if (
 | 
						|
      (eofChars & EEOFCHARACTERS_DROPBACKSLASH) != 0 &&
 | 
						|
      !!result.length &&
 | 
						|
      result.endsWith("\\")
 | 
						|
    ) {
 | 
						|
      result = result.slice(0, -1);
 | 
						|
    }
 | 
						|
 | 
						|
    // First, ignore EEOFCHARACTERS_DROPBACKSLASH.
 | 
						|
    let c = eofChars >> 1;
 | 
						|
 | 
						|
    // All of the remaining EOFCharacters bits represent appended characters,
 | 
						|
    // and the bits are in the order that they need appending.
 | 
						|
    for (const p of kImpliedEOFCharacters) {
 | 
						|
      if (c & 1) {
 | 
						|
        result += String.fromCharCode(p);
 | 
						|
      }
 | 
						|
      c >>= 1;
 | 
						|
    }
 | 
						|
 | 
						|
    return result;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
exports.InspectorCSSParserWrapper = InspectorCSSParserWrapper;
 |