.eslintrc.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. const { readGlobalsFile } = require('./scripts/webpack-util');
  2. const FILES_INJECTED = [`src/injected/**/*.js`];
  3. const FILES_CONTENT = [
  4. 'src/injected/*.js',
  5. 'src/injected/content/**/*.js',
  6. ];
  7. const FILES_WEB = [`src/injected/web/**/*.js`];
  8. /* Note that `injected` uses several more `common` files indirectly, but we check just these
  9. * two automatically because they are trivial by design and must always pass the check */
  10. const FILES_SHARED = [
  11. 'src/common/browser.js',
  12. 'src/common/consts.js',
  13. ];
  14. const GLOBALS_COMMON = getGlobals('src/common/safe-globals.js');
  15. const GLOBALS_INJECTED = getGlobals(`src/injected/safe-globals-injected.js`);
  16. const GLOBALS_CONTENT = {
  17. INIT_FUNC_NAME: false,
  18. ...getGlobals(`src/injected/content/safe-globals-content.js`),
  19. ...GLOBALS_INJECTED,
  20. };
  21. const GLOBALS_WEB = {
  22. ...getGlobals(`src/injected/web/safe-globals-web.js`),
  23. ...GLOBALS_INJECTED,
  24. IS_FIREFOX: false, // passed as a parameter to VMInitInjection in webpack.conf.js
  25. };
  26. const INJECTED_RULES = {
  27. 'no-restricted-imports': ['error', {
  28. patterns: ['*/common', '*/common/*'],
  29. }],
  30. 'no-restricted-syntax': [
  31. 'error', {
  32. selector: 'ObjectExpression > ExperimentalSpreadProperty',
  33. message: 'Object spread adds a polyfill in injected* even if unused by it',
  34. }, {
  35. selector: 'OptionalCallExpression',
  36. message: 'Optional call uses .call(), which may be spoofed/broken in an unsafe environment',
  37. // TODO: write a Babel plugin to use safeCall for this.
  38. }, {
  39. selector: 'ArrayPattern',
  40. message: 'Destructuring via Symbol.iterator may be spoofed/broken in an unsafe environment',
  41. }, {
  42. selector: ':matches(ArrayExpression, CallExpression) > SpreadElement',
  43. message: 'Spreading via Symbol.iterator may be spoofed/broken in an unsafe environment',
  44. }, {
  45. selector: '[callee.object.name="Object"], MemberExpression[object.name="Object"]',
  46. message: 'Using potentially spoofed methods in an unsafe environment',
  47. // TODO: auto-generate the rule using GLOBALS
  48. }, {
  49. selector: `CallExpression[callee.name="defineProperty"]:not(${[
  50. '[arguments.2.properties.0.key.name="__proto__"]',
  51. ':has(CallExpression[callee.name="createNullObj"])'
  52. ].join(',')})`,
  53. message: 'Prototype of descriptor may be spoofed/broken in an unsafe environment',
  54. }
  55. ],
  56. };
  57. module.exports = {
  58. root: true,
  59. extends: [
  60. require.resolve('@gera2ld/plaid/eslint'),
  61. require.resolve('@gera2ld/plaid-vue/eslint/vue'),
  62. ],
  63. parserOptions: {
  64. ecmaFeatures: {
  65. legacyDecorators: true,
  66. },
  67. },
  68. overrides: [{
  69. // `browser` is a local variable since we remove the global `chrome` and `browser` in injected*
  70. // to prevent exposing them to userscripts with `@inject-into content`
  71. files: ['*'],
  72. excludedFiles: [...FILES_INJECTED, ...FILES_SHARED],
  73. globals: {
  74. browser: false,
  75. ...GLOBALS_COMMON,
  76. },
  77. }, {
  78. files: FILES_SHARED,
  79. globals: GLOBALS_COMMON,
  80. }, {
  81. files: FILES_WEB,
  82. globals: GLOBALS_WEB,
  83. }, {
  84. files: FILES_CONTENT,
  85. globals: GLOBALS_CONTENT,
  86. }, {
  87. files: FILES_INJECTED,
  88. excludedFiles: [...FILES_CONTENT, ...FILES_WEB],
  89. // intersection of globals in CONTENT and WEB
  90. globals: Object.keys(GLOBALS_CONTENT).reduce((res, key) => (
  91. Object.assign(res, key in GLOBALS_WEB && { [key]: false })
  92. ), {}),
  93. }, {
  94. files: [...FILES_INJECTED, ...FILES_SHARED],
  95. rules: INJECTED_RULES,
  96. }, {
  97. files: FILES_WEB,
  98. rules: {
  99. ...INJECTED_RULES,
  100. 'no-restricted-syntax': [
  101. ...INJECTED_RULES['no-restricted-syntax'],
  102. {
  103. selector: '[regex], NewExpression[callee.name="RegExp"]',
  104. message: 'RegExp internally depends on a *ton* of stuff that may be spoofed or broken',
  105. // https://262.ecma-international.org/12.0/#sec-regexpexec
  106. },
  107. ],
  108. },
  109. }, {
  110. // build scripts
  111. files: [
  112. '*.js',
  113. 'scripts/*.js',
  114. 'scripts/*.mjs',
  115. ],
  116. env: { node: true },
  117. rules: {
  118. 'global-require': 0,
  119. 'import/newline-after-import': 0,
  120. 'import/no-extraneous-dependencies': 0, // spits errors in github action
  121. 'import/extensions': 0,
  122. }
  123. }, {
  124. files: ['*.vue'],
  125. rules: {
  126. 'vue/multi-word-component-names': 0,
  127. },
  128. }],
  129. rules: {
  130. 'import/extensions': ['error', 'ignorePackages', {
  131. js: 'never',
  132. vue: 'never',
  133. }],
  134. // copied from airbnb-base, replaced 4 with 8
  135. 'object-curly-newline': ['error', {
  136. ObjectExpression: { minProperties: 8, multiline: true, consistent: true },
  137. ObjectPattern: { minProperties: 8, multiline: true, consistent: true },
  138. ImportDeclaration: { minProperties: 8, multiline: true, consistent: true },
  139. ExportDeclaration: { minProperties: 8, multiline: true, consistent: true },
  140. }],
  141. },
  142. };
  143. function getGlobals(filename) {
  144. const res = {};
  145. const { ast } = readGlobalsFile(filename, { ast: true });
  146. ast.program.body.forEach(body => {
  147. const { declarations } = body.declaration || body;
  148. if (!declarations) return;
  149. declarations.forEach(function processId({
  150. id: {
  151. left,
  152. properties,
  153. name = left && left.name,
  154. },
  155. }) {
  156. if (name) {
  157. // const NAME = whatever
  158. res[name] = false;
  159. } else if (properties) {
  160. // const { NAME1, prototype: { NAME2: ALIAS2 } } = whatever
  161. properties.forEach(({ value }) => processId({ id: value }));
  162. }
  163. });
  164. });
  165. return res;
  166. }