Skip to content

Commit

Permalink
Refactor: pull out mozParser
Browse files Browse the repository at this point in the history
  • Loading branch information
eight04 committed Aug 14, 2017
1 parent 09a16ff commit d49f65b
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 131 deletions.
2 changes: 2 additions & 0 deletions edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
<script src="js/messaging.js"></script>
<script src="js/prefs.js"></script>
<script src="js/localization.js"></script>
<script src="js/script-loader.js"></script>
<script src="js/moz-parser.js"></script>
<script src="content/apply.js"></script>
<link rel="stylesheet" href="edit/edit.css">
<script src="edit/edit.js"></script>
Expand Down
154 changes: 23 additions & 131 deletions edit/edit.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint brace-style: 0, operator-linebreak: 0 */
/* global CodeMirror exports parserlib CSSLint */
/* global CodeMirror exports parserlib CSSLint mozParser */
'use strict';

let styleId = null;
Expand Down Expand Up @@ -1530,17 +1530,7 @@ function showMozillaFormat() {
}

function toMozillaFormat() {
return getSectionsHashes().map(section => {
let cssMds = [];
for (const i in propertyToCss) {
if (section[i]) {
cssMds = cssMds.concat(section[i].map(v =>
propertyToCss[i] + '("' + v.replace(/\\/g, '\\\\') + '")'
));
}
}
return cssMds.length ? '@-moz-document ' + cssMds.join(', ') + ' {\n' + section.code + '\n}' : section.code;
}).join('\n\n');
return mozParser.format({sections: getSectionsHashes()});
}

function fromMozillaFormat() {
Expand Down Expand Up @@ -1568,117 +1558,8 @@ function fromMozillaFormat() {
const replaceOldStyle = this.name === 'import-replace';
popup.querySelector('.dismiss').onclick();
const mozStyle = trimNewLines(popup.codebox.getValue());
const parser = new parserlib.css.Parser();
const lines = mozStyle.split('\n');
const sectionStack = [{code: '', start: {line: 1, col: 1}}];
const errors = [];
// let oldSectionCount = editors.length;
let firstAddedCM;

parser.addListener('startdocument', function (e) {
let outerText = getRange(sectionStack.last.start, (--e.col, e));
const gapComment = outerText.match(/(\/\*[\s\S]*?\*\/)[\s\n]*$/);
const section = {code: '', start: backtrackTo(this, parserlib.css.Tokens.LBRACE, 'end')};
// move last comment before @-moz-document inside the section
if (gapComment && !gapComment[1].match(/\/\*\s*AGENT_SHEET\s*\*\//)) {
section.code = gapComment[1] + '\n';
outerText = trimNewLines(outerText.substring(0, gapComment.index));
}
if (outerText.trim()) {
sectionStack.last.code = outerText;
doAddSection(sectionStack.last);
sectionStack.last.code = '';
}
for (const f of e.functions) {
const m = f && f.match(/^([\w-]*)\((['"]?)(.+?)\2?\)$/);
if (!m || !/^(url|url-prefix|domain|regexp)$/.test(m[1])) {
errors.push(`${e.line}:${e.col + 1} invalid function "${m ? m[1] : f || ''}"`);
continue;
}
const aType = CssToProperty[m[1]];
const aValue = aType !== 'regexps' ? m[3] : m[3].replace(/\\\\/g, '\\');
(section[aType] = section[aType] || []).push(aValue);
}
sectionStack.push(section);
});

parser.addListener('enddocument', function () {
const end = backtrackTo(this, parserlib.css.Tokens.RBRACE, 'start');
const section = sectionStack.pop();
section.code += getRange(section.start, end);
sectionStack.last.start = (++end.col, end);
doAddSection(section);
});

parser.addListener('endstylesheet', () => {
// add nonclosed outer sections (either broken or the last global one)
const endOfText = {line: lines.length, col: lines.last.length + 1};
sectionStack.last.code += getRange(sectionStack.last.start, endOfText);
sectionStack.forEach(doAddSection);

delete maximizeCodeHeight.stats;
editors.forEach(cm => {
maximizeCodeHeight(cm.getSection(), cm === editors.last);
});

makeSectionVisible(firstAddedCM);
firstAddedCM.focus();

if (errors.length) {
showHelp(t('issues'), $element({
tag: 'pre',
textContent: errors.join('\n'),
}));
}
});

parser.addListener('error', e => {
errors.push(e.line + ':' + e.col + ' ' +
e.message.replace(/ at line \d.+$/, ''));
});

parser.parse(mozStyle);

function getRange(start, end) {
const L1 = start.line - 1;
const C1 = start.col - 1;
const L2 = end.line - 1;
const C2 = end.col - 1;
if (L1 === L2) {
return lines[L1].substr(C1, C2 - C1 + 1);
} else {
const middle = lines.slice(L1 + 1, L2).join('\n');
return lines[L1].substr(C1) + '\n' + middle +
(L2 >= lines.length ? '' : ((middle ? '\n' : '') + lines[L2].substring(0, C2)));
}
}
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;
}
if (!firstAddedCM) {
if (!initFirstSection(section)) {
return;
}
}
setCleanItem(addSection(null, section), false);
firstAddedCM = firstAddedCM || editors.last;
}
// do onetime housekeeping as the imported text is confirmed to be a valid style
function initFirstSection(section) {
// skip adding the first global section when there's no code/comments
if (!section.code.replace('@namespace url(http://www.w3.org/1999/xhtml);', '') /* ignore boilerplate NS */
.replace(/[\s\n]/g, '')) { /* ignore all whitespace including new lines */
return false;
}
mozParser.parse(mozStyle).then(sections => {
if (replaceOldStyle) {
editors.slice(0).reverse().forEach(cm => {
removeSection({target: cm.getSection().firstElementChild});
Expand All @@ -1689,16 +1570,27 @@ function fromMozillaFormat() {
removeSection({target: editors.last.getSection()});
}
}
return true;
}
}
function backtrackTo(parser, tokenType, startEnd) {
const tokens = parser._tokenStream._lt;
for (let i = parser._tokenStream._ltIndex - 1; i >= 0; --i) {
if (tokens[i].type === tokenType) {
return {line: tokens[i][startEnd + 'Line'], col: tokens[i][startEnd + 'Col']};

const firstSection = sections[0];
setCleanItem(addSection(null, firstSection), false);
const firstAddedCM = editors.last;
for (const section of sections.slice(1)) {
setCleanItem(addSection(null, section), false);
}
}

delete maximizeCodeHeight.stats;
editors.forEach(cm => {
maximizeCodeHeight(cm.getSection(), cm === editors.last);
});

makeSectionVisible(firstAddedCM);
firstAddedCM.focus();
}, errors => {
showHelp(t('issues'), $element({
tag: 'pre',
textContent: errors.join('\n'),
}));
});
}
function trimNewLines(s) {
return s.replace(/^[\s\n]+/, '').replace(/[\s\n]+$/, '');
Expand Down
147 changes: 147 additions & 0 deletions js/moz-parser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/* globals parserlib, loadScript */

'use strict';

// eslint-disable-next-line no-var
var mozParser = (function(){
// direct & reverse mapping of @-moz-document keywords and internal property names
const propertyToCss = {urls: 'url', urlPrefixes: 'url-prefix', domains: 'domain', regexps: 'regexp'};
const CssToProperty = {'url': 'urls', 'url-prefix': 'urlPrefixes', 'domain': 'domains', 'regexp': 'regexps'};

function backtrackTo(parser, tokenType, startEnd) {
const tokens = parser._tokenStream._lt;
for (let i = parser._tokenStream._ltIndex - 1; i >= 0; --i) {
if (tokens[i].type === tokenType) {
return {line: tokens[i][startEnd + 'Line'], col: tokens[i][startEnd + 'Col']};
}
}
}

function trimNewLines(s) {
return s.replace(/^[\s\n]+/, '').replace(/[\s\n]+$/, '');
}

function parseMozFormat(mozStyle) {
return new Promise((resolve, reject) => {
const parser = new parserlib.css.Parser();
const lines = mozStyle.split('\n');
const sectionStack = [{code: '', start: {line: 1, col: 1}}];
const errors = [];
const sections = [];

parser.addListener('startdocument', function (e) {
let outerText = getRange(sectionStack.last.start, (--e.col, e));
const gapComment = outerText.match(/(\/\*[\s\S]*?\*\/)[\s\n]*$/);
const section = {code: '', start: backtrackTo(this, parserlib.css.Tokens.LBRACE, 'end')};
// move last comment before @-moz-document inside the section
if (gapComment && !gapComment[1].match(/\/\*\s*AGENT_SHEET\s*\*\//)) {
section.code = gapComment[1] + '\n';
outerText = trimNewLines(outerText.substring(0, gapComment.index));
}
if (outerText.trim()) {
sectionStack.last.code = outerText;
doAddSection(sectionStack.last);
sectionStack.last.code = '';
}
for (const f of e.functions) {
const m = f && f.match(/^([\w-]*)\((['"]?)(.+?)\2?\)$/);
if (!m || !/^(url|url-prefix|domain|regexp)$/.test(m[1])) {
errors.push(`${e.line}:${e.col + 1} invalid function "${m ? m[1] : f || ''}"`);
continue;
}
const aType = CssToProperty[m[1]];
const aValue = aType !== 'regexps' ? m[3] : m[3].replace(/\\\\/g, '\\');
(section[aType] = section[aType] || []).push(aValue);
}
sectionStack.push(section);
});

parser.addListener('enddocument', function () {
const end = backtrackTo(this, parserlib.css.Tokens.RBRACE, 'start');
const section = sectionStack.pop();
section.code += getRange(section.start, end);
sectionStack.last.start = (++end.col, end);
doAddSection(section);
});

parser.addListener('endstylesheet', () => {
// add nonclosed outer sections (either broken or the last global one)
const endOfText = {line: lines.length, col: lines.last.length + 1};
sectionStack.last.code += getRange(sectionStack.last.start, endOfText);
sectionStack.forEach(doAddSection);


if (errors.length) {
reject(errors);
} else {
resolve(sections);
}
});

parser.addListener('error', e => {
errors.push(e.line + ':' + e.col + ' ' +
e.message.replace(/ at line \d.+$/, ''));
});

parser.parse(mozStyle);

function getRange(start, end) {
const L1 = start.line - 1;
const C1 = start.col - 1;
const L2 = end.line - 1;
const C2 = end.col - 1;
if (L1 === L2) {
return lines[L1].substr(C1, C2 - C1 + 1);
} else {
const middle = lines.slice(L1 + 1, L2).join('\n');
return lines[L1].substr(C1) + '\n' + middle +
(L2 >= lines.length ? '' : ((middle ? '\n' : '') + lines[L2].substring(0, C2)));
}
}

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));
}
});
}

return {
// Parse mozilla-format userstyle into sections
parse(text) {
if (typeof parserlib === 'undefined') {
return loadScript('vender/csslint/csslint-worker.js')
.then(() => parseMozFormat(text));
}
return parseMozFormat(text);
},
format(style) {
return style.sections.map(section => {
let cssMds = [];
for (const i in propertyToCss) {
if (section[i]) {
cssMds = cssMds.concat(section[i].map(v =>
propertyToCss[i] + '("' + v.replace(/\\/g, '\\\\') + '")'
));
}
}
return cssMds.length ? '@-moz-document ' + cssMds.join(', ') + ' {\n' + section.code + '\n}' : section.code;
}).join('\n\n');
}
};
})();

0 comments on commit d49f65b

Please sign in to comment.