| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- 'use strict';
- require([
- '/js/csslint/parserlib', /* global parserlib */
- '/js/sections-util', /* global MozDocMapper */
- ]);
- /* exported extractSections */
- /**
- * Extracts @-moz-document blocks into sections and the code between them into global sections.
- * Puts the global comments into the following section to minimize the amount of global sections.
- * Doesn't move the comment with ==UserStyle== inside.
- * @param {Object} _
- * @param {string} _.code
- * @param {boolean} [_.fast] - uses topDocOnly option to extract sections as text
- * @param {number} [_.styleId] - used to preserve parserCache on subsequent runs over the same style
- * @returns {{sections: Array, errors: Array}}
- * @property {?number} lastStyleId
- */
- function extractSections({code, styleId, fast = true}) {
- const hasSingleEscapes = /([^\\]|^)\\([^\\]|$)/;
- const parser = new parserlib.css.Parser({
- starHack: true,
- skipValidation: true,
- topDocOnly: fast,
- });
- const sectionStack = [{code: '', start: 0}];
- const errors = [];
- const sections = [];
- const mozStyle = code.replace(/\r\n?/g, '\n'); // same as parserlib.StringReader
- parser.addListener('startdocument', e => {
- const lastSection = sectionStack[sectionStack.length - 1];
- let outerText = mozStyle.slice(lastSection.start, e.offset);
- const lastCmt = getLastComment(outerText);
- const section = {
- code: '',
- start: parser._tokenStream._token.offset + 1,
- };
- // move last comment before @-moz-document inside the section
- if (!lastCmt.includes('AGENT_SHEET') &&
- !/==userstyle==/i.test(lastCmt)) {
- if (lastCmt) {
- section.code = lastCmt + '\n';
- outerText = outerText.slice(0, -lastCmt.length);
- }
- outerText = outerText.match(/^\s*/)[0] + outerText.trim();
- }
- if (outerText.trim()) {
- lastSection.code = outerText;
- doAddSection(lastSection);
- lastSection.code = '';
- }
- for (const {name, expr, uri} of e.functions) {
- const aType = MozDocMapper.FROM_CSS[name.toLowerCase()];
- const p0 = expr && expr.parts[0];
- if (p0 && aType === 'regexps') {
- const s = p0.text;
- if (hasSingleEscapes.test(p0.text)) {
- const isQuoted = /^['"]/.test(s) && s.endsWith(s[0]);
- p0.value = isQuoted ? s.slice(1, -1) : s;
- }
- }
- (section[aType] = section[aType] || []).push(uri || p0 && p0.value || '');
- }
- sectionStack.push(section);
- });
- parser.addListener('enddocument', e => {
- const section = sectionStack.pop();
- const lastSection = sectionStack[sectionStack.length - 1];
- section.code += mozStyle.slice(section.start, e.offset);
- lastSection.start = e.offset + 1;
- doAddSection(section);
- });
- parser.addListener('endstylesheet', () => {
- // add nonclosed outer sections (either broken or the last global one)
- const lastSection = sectionStack[sectionStack.length - 1];
- lastSection.code += mozStyle.slice(lastSection.start);
- sectionStack.forEach(doAddSection);
- });
- parser.addListener('error', e => {
- errors.push(e);
- });
- try {
- parser.parse(mozStyle, {
- reuseCache: !extractSections.lastStyleId || styleId === extractSections.lastStyleId,
- });
- } catch (e) {
- errors.push(e);
- }
- for (const err of errors) {
- for (const [k, v] of Object.entries(err)) {
- if (typeof v === 'object') delete err[k];
- }
- err.message = `${err.line}:${err.col} ${err.message}`;
- }
- extractSections.lastStyleId = styleId;
- return {sections, errors};
- function doAddSection(section) {
- section.code = section.code.trim();
- // don't add empty sections
- if (
- !section.code &&
- !section.urls &&
- !section.urlPrefixes &&
- !section.domains &&
- !section.regexps
- ) {
- return;
- }
- /* ignore boilerplate NS */
- if (section.code === '@namespace url(http://www.w3.org/1999/xhtml);') {
- return;
- }
- sections.push(Object.assign({}, section));
- }
- function getLastComment(text) {
- let open = text.length;
- let close;
- while (open) {
- // at this point we're guaranteed to be outside of a comment
- close = text.lastIndexOf('*/', open - 2);
- if (close < 0) {
- break;
- }
- // stop if a non-whitespace precedes and return what we currently have
- const tailEmpty = !text.substring(close + 2, open).trim();
- if (!tailEmpty) {
- break;
- }
- // find a closed preceding comment
- const prevClose = text.lastIndexOf('*/', close - 2);
- // then find the real start of current comment
- // e.g. /* preceding */ /* current /* current /* current */
- open = text.indexOf('/*', prevClose < 0 ? 0 : prevClose + 2);
- }
- return open ? text.slice(open) : text;
- }
- }
|