Browse Source

reuse meta parser regexp and require a single space after // (#619)

* '//' + single ASCII space is apparently hardcoded in almost every userscript-related app/site
* we allow whitespace before '//' like TM and greasyfork.org do
tophf 6 years ago
parent
commit
3933a41ee1
4 changed files with 31 additions and 36 deletions
  1. 12 26
      src/background/utils/script.js
  2. 6 0
      src/common/consts.js
  3. 2 7
      src/injected/web/index.js
  4. 11 3
      test/background/script.test.js

+ 12 - 26
src/background/utils/script.js

@@ -1,12 +1,10 @@
 import { encodeFilename } from '#/common';
+import { METABLOCK_RE } from '#/common/consts';
 import { getOption } from './options';
 
-const metaStart = '==UserScript==';
-const metaEnd = '==/UserScript==';
-
 export function isUserScript(text) {
   if (/^\s*</.test(text)) return false; // HTML
-  if (text.indexOf(metaStart) < 0) return false; // Lack of meta block
+  if (text.indexOf('// ==UserScript==') < 0) return false; // Lack of meta block
   return true;
 }
 
@@ -47,28 +45,16 @@ export function parseMeta(code) {
   .reduce((res, key) => Object.assign(res, {
     [key]: metaTypes[key].default(),
   }), {});
-  let flag = -1;
-  // Allow metadata lines to start with SPACE? '//' SPACE?
-  // Allow anything to follow the predefined text of the metaStart/End
-  // The spaces must be on the same line so [\t\x20] is used as \s also matches \r\n
-  code.replace(/(?:^|\n)\s*\/\/[\t\x20]*([@=]\S+)(.*)/g, (_match, group1, group2) => {
-    if (flag < 0 && group1.startsWith(metaStart)) {
-      // start meta
-      flag = 1;
-    } else if (flag > 0 && group1.startsWith(metaEnd)) {
-      // end meta
-      flag = 0;
-    }
-    if (flag === 1 && group1.startsWith('@')) {
-      const [keyName, locale] = group1.slice(1).split(':');
-      const camelKey = keyName.replace(/[-_](\w)/g, (m, g) => g.toUpperCase());
-      const key = locale ? `${camelKey}:${locale.toLowerCase()}` : camelKey;
-      const val = group2.trim();
-      const metaType = metaTypes[key] || defaultType;
-      let oldValue = meta[key];
-      if (typeof oldValue === 'undefined') oldValue = metaType.default();
-      meta[key] = metaType.transform(oldValue, val);
-    }
+  const metaBody = code.match(METABLOCK_RE)[1] || '';
+  metaBody.replace(/(?:^|\n)\s*\/\/\x20(@\S+)(.*)/g, (_match, rawKey, rawValue) => {
+    const [keyName, locale] = rawKey.slice(1).split(':');
+    const camelKey = keyName.replace(/[-_](\w)/g, (m, g) => g.toUpperCase());
+    const key = locale ? `${camelKey}:${locale.toLowerCase()}` : camelKey;
+    const val = rawValue.trim();
+    const metaType = metaTypes[key] || defaultType;
+    let oldValue = meta[key];
+    if (typeof oldValue === 'undefined') oldValue = metaType.default();
+    meta[key] = metaType.transform(oldValue, val);
   });
   meta.resources = meta.resource;
   delete meta.resource;

+ 6 - 0
src/common/consts.js

@@ -4,3 +4,9 @@ export const INJECT_AUTO = 'auto';
 
 export const CMD_SCRIPT_ADD = 'AddScript';
 export const CMD_SCRIPT_UPDATE = 'UpdateScript';
+
+// Allow metadata lines to start with WHITESPACE? '//' SPACE
+// Allow anything to follow the predefined text of the metaStart/End
+// The SPACE must be on the same line and specifically \x20 as \s would also match \r\n\t
+// Note: when there's no valid metablock, an empty string is matched for convenience
+export const METABLOCK_RE = /(?:^|\n)\s*\/\/\x20==UserScript==([\s\S]*?\n)\s*\/\/\x20==\/UserScript==|$/;

+ 2 - 7
src/injected/web/index.js

@@ -1,4 +1,4 @@
-import { INJECT_PAGE, INJECT_CONTENT } from '#/common/consts';
+import { INJECT_PAGE, INJECT_CONTENT, METABLOCK_RE } from '#/common/consts';
 import {
   getUniqId, bindEvents, attachFunction, cache2blobUrl,
 } from '../utils';
@@ -185,14 +185,9 @@ function wrapGM(script, code, cache, unsafeWindow) {
     b: val => val === 'true',
   };
   const pathMap = script.custom.pathMap || {};
-  // Allow metadata lines to start with SPACE? '//' SPACE?
-  // Allow anything to follow the predefined text of the metaStart/End
-  // The spaces must be on the same line so [\t\x20] is used as \s also matches \r\n
-  const matches = code.match(/(?:^|\n)\s*\/\/[\t\x20]*==UserScript==.*\n([\s\S]*?\n|)\s*\/\/[\t\x20]*==\/UserScript==/);
-  const metaStr = matches ? matches[1] : '';
   const gmInfo = {
     uuid: script.props.uuid,
-    scriptMetaStr: metaStr,
+    scriptMetaStr: code.match(METABLOCK_RE)[1] || '',
     scriptWillUpdate: !!script.config.shouldUpdate,
     scriptHandler: 'Violentmonkey',
     version: bridge.version,

+ 11 - 3
test/background/script.test.js

@@ -48,16 +48,24 @@ test('parseMeta', (t) => {
 
 test('parseMetaIrregularities', (t) => {
   t.deepEqual(parseMeta(`\
-  //    ==UserScript==============
+  // ==UserScript==============
 // @name foo
- //@namespace bar
-//==/UserScript===================
+ // @namespace bar
+// ==/UserScript===================
   `), {
     ...baseMeta,
     name: 'foo',
     namespace: 'bar',
   });
   t.deepEqual(parseMeta(`\
+// ==UserScript==
+//@name foo
+// ==/UserScript==`), baseMeta);
+  t.deepEqual(parseMeta(`\
+//==UserScript==
+// @name foo
+//\t==/UserScript==`), baseMeta);
+  t.deepEqual(parseMeta(`\
 /*
 //
   ==UserScript==