<commit_before>/**
 * @license MIT License
 *
 * Copyright (c) 2015 Tetsuharu OHZEKI <saneyuki.snyk@gmail.com>
 * Copyright (c) 2015 Yusuke Suzuki <utatane.tea@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
/*eslint quote-props: [2, "always"] */

'use strict';

// ESLint Configuration Files enables to include comments.
// http://eslint.org/docs/configuring/#comments-in-configuration-files
module.exports = {
    'extends': 'eslint:recommended',

    'ecmaFeatures': {
        'arrowFunctions': true,
        'blockBindings': true,
        'classes': true,
        'objectLiteralComputedProperties': true,
        'objectLiteralDuplicateProperties': true,
        'objectLiteralShorthandMethods': true,
        'objectLiteralShorthandProperties': true,
        'spread': true,
        'templateStrings': true,
    },

    'env': {
        'node': true
    },

    'plugins': [
        'react' // for React/JSX
    ],

    'root': true,

    'rules': {
        // Possible Errors
        'comma-dangle': [0, 'never'],
        'no-cond-assign': 2,
        'no-console': 0,
        'no-constant-condition': 1,
        'no-control-regex': 2,
        'no-debugger': 1,
        'no-dupe-args': 2,
        'no-dupe-keys': 2,
        'no-duplicate-case': 2,
        'no-empty-character-class': 2,
        'no-empty': 2,
        'no-ex-assign': 2,
        'no-extra-boolean-cast': 0,
        'no-extra-parens': 0,
        'no-extra-semi': 1,
        'no-func-assign': 2,
        'no-inner-declarations': 2,
        'no-invalid-regexp': 2,
        'no-irregular-whitespace': 2,
        'no-negated-in-lhs': 2,
        'no-obj-calls': 2,
        'no-regex-spaces': 2,
        'no-sparse-arrays': 2,
        'no-unexpected-multiline': 1,
        'no-unreachable': 1,
        'use-isnan': 2,
        'valid-jsdoc': [2, {
            'requireReturn': true,
            'requireParamDescription': false,
            'requireReturnDescription': false
        }],
        'valid-typeof': 2,

        // Best Practices
        'block-scoped-var': 1,
        'consistent-return': 2,
        'curly': 2,
        'eqeqeq': 2,
        'no-alert': 1,
        'no-caller': 2,
        'no-case-declarations': 2,
        'no-div-regex': 2,
        'no-empty-pattern': 2,
        'no-eq-null': 2,
        'no-eval': 2,
        'no-fallthrough': 2,
        'no-implied-eval': 2,
        'no-invalid-this': 1,
        'no-new-func': 1,
        'no-new-wrappers': 2,
        'no-param-reassign': [1, {
            'props': true
        }],
        'no-octal': 2,
        'no-proto': 2,
        'no-redeclare': 2,
        'no-return-assign': 2,
        'no-script-url': 2,
        'no-throw-literal': 2,
        'no-unused-expressions': 2,
        'no-useless-call': 1,
        'no-useless-concat': 1,
        'no-with': 2,
        'radix': 2,

        // Strict Mode
        'strict': [2, 'global'],

        // Variables
        'init-declarations': [2, 'always'],
        'no-catch-shadow': 2,
        'no-delete-var': 2,
        'no-shadow': 0,
        'no-undef': 2,
        'no-undef-init': 2,
        'no-undefined': 0,
        'no-unused-vars': [1, {
            'vars': 'all',
            'args': 'after-used',
        }],
        'no-use-before-define': 0,

        // Node.js
        'global-require': 2,
        'no-mixed-requires': [2, {
            'grouping': true,
        }],
        'no-new-require': 2,
        'no-path-concat': 2,
        'no-process-exit': 0,
        'no-sync': 1, // Bann to use sync method. FIXME: enable this rules as an error.

        // Stylistic Issues
        'camelcase': [2, {
            'properties': 'always'
        }],
        'comma-spacing': [2, {
            'before': false,
            'after': true
        }],
        'comma-style': [2, 'last'],
        'computed-property-spacing': [2, 'never'],
        'indent': [2, 4, {
            'SwitchCase': 1
        }],
        'jsx-quotes': [1, 'prefer-single'],
        'key-spacing': 0,
        'linebreak-style': [2, 'unix'],
        'new-cap': 1,
        'new-parens': 2,
        'no-array-constructor': 2, // In almost case, we don't have to use `new Array()` without any comments.
        'no-mixed-spaces-and-tabs': 2,
        'no-new-object': 2, // In almost case, we don't have to use `new Object()` without any comments.
        'no-spaced-func': 2,
        'no-trailing-spaces': 2,
        'no-underscore-dangle': 0,
        'no-unneeded-ternary': 2,
        'operator-linebreak': [2, 'after'],
        'quotes': [2, 'single', 'avoid-escape'],
        'semi': [2, 'always'],
        'semi-spacing':[2, {
            'before': false,
            'after': true
        }],
        'space-after-keywords': 1,
        'space-infix-ops': 1,
        'space-return-throw-case': 1,
        'space-unary-ops': [2, {
            'words': true,
            'nonwords': false
        }],
        'spaced-comment': 0,

        // ECMAScript 6
        'arrow-body-style': [1, 'as-needed'],
        'arrow-parens': 1,
        'arrow-spacing': [1, {
            'before': true,
            'after': true
        }],
        'constructor-super': 2,
        'generator-star-spacing': [2, {
            'before': false,
            'after': true
        }],
        'no-arrow-condition': 2,
        'no-class-assign': 2,
        'no-const-assign': 2,
        'no-dupe-class-members': 2,
        'no-this-before-super': 2,
        'no-var': 1,
        'object-shorthand': 0,
        'prefer-const': 1,
        'prefer-reflect': 1,
        'prefer-spread': 1,
        'require-yield': 2,

        // ESLint-plugin-React
        // https://github.com/yannickcr/eslint-plugin-react
        'react/display-name': 0, // JSX transpiler creates displayName automatically.
        'react/forbid-prop-types': 0,
        'react/jsx-boolean-value': [2, 'always'], // Force boolean attribute explicitly.
        'react/jsx-closing-bracket-location': 0,
        'react/jsx-curly-spacing': 1,
        'react/jsx-indent-props': 0,
        'react/jsx-key': 1,
        'react/jsx-max-props-per-line': 0,
        'react/jsx-no-bind': 2,
        'react/jsx-no-duplicate-props': 2,
        'react/jsx-no-literals': 0,
        'react/jsx-no-undef': 2,
        'react/jsx-sort-props': 0,
        'react/jsx-sort-prop-types': [0, {
            'callbacksLast': true
        }],
        'react/jsx-uses-react': 1,
        'react/jsx-uses-vars': 1,
        'react/jsx-pascal-case': 2,
        'react/no-danger': 1,
        'react/no-did-mount-set-state': [1, 'allow-in-func'],
        'react/no-did-update-set-state': [1, 'allow-in-func'],
        'react/no-direct-mutation-state': 1,
        'react/no-multi-comp': 0,
        'react/no-set-state': 0, // FIXME: Enable this rule as a waring
        'react/no-unknown-property': 2,
        'react/prefer-es6-class': 2,
        'react/prop-types': 1,
        'react/react-in-jsx-scope': 1,
        'react/require-extension': 0,
        'react/self-closing-comp': 2,
        'react/sort-comp': [1, {
            'order': [
                'constructor',
                'metadata',
                'rendering',
                'lifecycle',
                'everything-else'
            ],
            'groups': {
                'metadata': [
                    'displayName',
                    'propTypes',
                    'contextTypes',
                    'childContextTypes',
                    'mixins',
                    'getDefaultProps',
                    'getInitialState',
                    'getChildContext'
                ],
                'rendering': [
                    'render',
                    '/^render.+$/'
                ]
            }
        }],
        'react/wrap-multilines': 2
    }
};
<commit_msg>Auto merge of #501 - saneyuki:lint, r=saneyuki<commit_after>/**
 * @license MIT License
 *
 * Copyright (c) 2015 Tetsuharu OHZEKI <saneyuki.snyk@gmail.com>
 * Copyright (c) 2015 Yusuke Suzuki <utatane.tea@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
/*eslint quote-props: [2, "always"] */

'use strict';

// ESLint Configuration Files enables to include comments.
// http://eslint.org/docs/configuring/#comments-in-configuration-files
module.exports = {

    // Derive recommended rules to detect bad smells even if eslint added a new recommended one but we forgot to add them to ours.
    'extends': 'eslint:recommended',

    'ecmaFeatures': {
        'arrowFunctions': true,
        'blockBindings': true,
        'classes': true,
        'objectLiteralComputedProperties': true,
        'objectLiteralDuplicateProperties': true,
        'objectLiteralShorthandMethods': true,
        'objectLiteralShorthandProperties': true,
        'spread': true,
        'templateStrings': true,
    },

    'env': {
        'node': true
    },

    'plugins': [
        'react' // for React/JSX
    ],

    'root': true,

    'rules': {
        // Possible Errors
        'comma-dangle': [0, 'never'],
        'no-cond-assign': 2,
        'no-console': 0,
        'no-constant-condition': 1,
        'no-control-regex': 2,
        'no-debugger': 1,
        'no-dupe-args': 2,
        'no-dupe-keys': 2,
        'no-duplicate-case': 2,
        'no-empty-character-class': 2,
        'no-empty': 2,
        'no-ex-assign': 2,
        'no-extra-boolean-cast': 0,
        'no-extra-parens': 0,
        'no-extra-semi': 1,
        'no-func-assign': 2,
        'no-inner-declarations': 2,
        'no-invalid-regexp': 2,
        'no-irregular-whitespace': 2,
        'no-negated-in-lhs': 2,
        'no-obj-calls': 2,
        'no-regex-spaces': 2,
        'no-sparse-arrays': 2,
        'no-unexpected-multiline': 1,
        'no-unreachable': 1,
        'use-isnan': 2,
        'valid-jsdoc': [2, {
            'requireReturn': true,
            'requireParamDescription': false,
            'requireReturnDescription': false
        }],
        'valid-typeof': 2,

        // Best Practices
        'block-scoped-var': 1,
        'consistent-return': 2,
        'curly': 2,
        'eqeqeq': 2,
        'no-alert': 1,
        'no-caller': 2,
        'no-case-declarations': 2,
        'no-div-regex': 2,
        'no-empty-pattern': 2,
        'no-eq-null': 2,
        'no-eval': 2,
        'no-fallthrough': 2,
        'no-implied-eval': 2,
        'no-invalid-this': 1,
        'no-new-func': 1,
        'no-new-wrappers': 2,
        'no-param-reassign': [1, {
            'props': true
        }],
        'no-octal': 2,
        'no-proto': 2,
        'no-redeclare': 2,
        'no-return-assign': 2,
        'no-script-url': 2,
        'no-throw-literal': 2,
        'no-unused-expressions': 2,
        'no-useless-call': 1,
        'no-useless-concat': 1,
        'no-with': 2,
        'radix': 2,

        // Strict Mode
        'strict': [2, 'global'],

        // Variables
        'init-declarations': [2, 'always'],
        'no-catch-shadow': 2,
        'no-delete-var': 2,
        'no-shadow': 0,
        'no-undef': 2,
        'no-undef-init': 2,
        'no-undefined': 0,
        'no-unused-vars': [1, {
            'vars': 'all',
            'args': 'after-used',
        }],
        'no-use-before-define': 0,

        // Node.js
        'global-require': 2,
        'no-mixed-requires': [2, {
            'grouping': true,
        }],
        'no-new-require': 2,
        'no-path-concat': 2,
        'no-process-exit': 0,
        'no-sync': 1, // Bann to use sync method. FIXME: enable this rules as an error.

        // Stylistic Issues
        'camelcase': [2, {
            'properties': 'always'
        }],
        'comma-spacing': [2, {
            'before': false,
            'after': true
        }],
        'comma-style': [2, 'last'],
        'computed-property-spacing': [2, 'never'],
        'indent': [2, 4, {
            'SwitchCase': 1
        }],
        'jsx-quotes': [1, 'prefer-single'],
        'key-spacing': 0,
        'linebreak-style': [2, 'unix'],
        'new-cap': 1,
        'new-parens': 2,
        'no-array-constructor': 2, // In almost case, we don't have to use `new Array()` without any comments.
        'no-mixed-spaces-and-tabs': 2,
        'no-new-object': 2, // In almost case, we don't have to use `new Object()` without any comments.
        'no-spaced-func': 2,
        'no-trailing-spaces': 2,
        'no-underscore-dangle': 0,
        'no-unneeded-ternary': 2,
        'operator-linebreak': [2, 'after'],
        'quotes': [2, 'single', 'avoid-escape'],
        'semi': [2, 'always'],
        'semi-spacing':[2, {
            'before': false,
            'after': true
        }],
        'space-after-keywords': 1,
        'space-infix-ops': 1,
        'space-return-throw-case': 1,
        'space-unary-ops': [2, {
            'words': true,
            'nonwords': false
        }],
        'spaced-comment': 0,

        // ECMAScript 6
        'arrow-body-style': [1, 'as-needed'],
        'arrow-parens': 1,
        'arrow-spacing': [1, {
            'before': true,
            'after': true
        }],
        'constructor-super': 2,
        'generator-star-spacing': [2, {
            'before': false,
            'after': true
        }],
        'no-arrow-condition': 2,
        'no-class-assign': 2,
        'no-const-assign': 2,
        'no-dupe-class-members': 2,
        'no-this-before-super': 2,
        'no-var': 1,
        'object-shorthand': 0,
        'prefer-const': 1,
        'prefer-reflect': 1,
        'prefer-spread': 1,
        'require-yield': 2,

        // ESLint-plugin-React
        // https://github.com/yannickcr/eslint-plugin-react
        'react/display-name': 0, // JSX transpiler creates displayName automatically.
        'react/forbid-prop-types': 0,
        'react/jsx-boolean-value': [2, 'always'], // Force boolean attribute explicitly.
        'react/jsx-closing-bracket-location': 0,
        'react/jsx-curly-spacing': 1,
        'react/jsx-indent-props': 0,
        'react/jsx-key': 1,
        'react/jsx-max-props-per-line': 0,
        'react/jsx-no-bind': 2,
        'react/jsx-no-duplicate-props': 2,
        'react/jsx-no-literals': 0,
        'react/jsx-no-undef': 2,
        'react/jsx-sort-props': 0,
        'react/jsx-sort-prop-types': [0, {
            'callbacksLast': true
        }],
        'react/jsx-uses-react': 1,
        'react/jsx-uses-vars': 1,
        'react/jsx-pascal-case': 2,
        'react/no-danger': 1,
        'react/no-did-mount-set-state': [1, 'allow-in-func'],
        'react/no-did-update-set-state': [1, 'allow-in-func'],
        'react/no-direct-mutation-state': 1,
        'react/no-multi-comp': 0,
        'react/no-set-state': 0, // FIXME: Enable this rule as a waring
        'react/no-unknown-property': 2,
        'react/prefer-es6-class': 2,
        'react/prop-types': 1,
        'react/react-in-jsx-scope': 1,
        'react/require-extension': 0,
        'react/self-closing-comp': 2,
        'react/sort-comp': [1, {
            'order': [
                'constructor',
                'metadata',
                'rendering',
                'lifecycle',
                'everything-else'
            ],
            'groups': {
                'metadata': [
                    'displayName',
                    'propTypes',
                    'contextTypes',
                    'childContextTypes',
                    'mixins',
                    'getDefaultProps',
                    'getInitialState',
                    'getChildContext'
                ],
                'rendering': [
                    'render',
                    '/^render.+$/'
                ]
            }
        }],
        'react/wrap-multilines': 2
    }
};
<|endoftext|>


<commit_before>module.exports = {
  env: {
    browser: true,
    es6: true,
    commonjs: true,
    node: true,
    'jest/globals': true
  },
  root: true,
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/eslint-recommended',
    'plugin:@typescript-eslint/recommended',
    'prettier/@typescript-eslint',
    'plugin:react/recommended',
    'plugin:jest/recommended'
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    project: 'tsconfig.eslint.json'
  },
  plugins: ['@typescript-eslint', 'jest'],
  rules: {
    '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
    '@typescript-eslint/naming-convention': [
      'error',
      {
        selector: 'interface',
        format: ['PascalCase'],
        custom: {
          regex: '^I[A-Z]',
          match: true
        }
      }
    ],
    '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }],
    '@typescript-eslint/no-use-before-define': 'off',
    '@typescript-eslint/camelcase': 'off',
    '@typescript-eslint/no-explicit-any': 'off',
    '@typescript-eslint/no-non-null-assertion': 'off',
    '@typescript-eslint/no-namespace': 'off',
    '@typescript-eslint/interface-name-prefix': 'off',
    '@typescript-eslint/explicit-function-return-type': 'off',
    '@typescript-eslint/ban-ts-comment': ['warn', { 'ts-ignore': true }],
    '@typescript-eslint/ban-types': 'warn',
    '@typescript-eslint/no-non-null-asserted-optional-chain': 'warn',
    '@typescript-eslint/no-var-requires': 'off',
    '@typescript-eslint/no-empty-interface': 'off',
    '@typescript-eslint/triple-slash-reference': 'warn',
    '@typescript-eslint/no-inferrable-types': 'off',
    'no-inner-declarations': 'off',
    'no-prototype-builtins': 'off',
    'no-control-regex': 'warn',
    'no-undef': 'warn',
    'no-case-declarations': 'warn',
    'no-useless-escape': 'off',
    'prefer-const': 'off',
    'jest/no-jest-import': 'off',
    'jest/no-export': 'warn',
    'jest/no-try-expect': 'warn',
    'jest/expect-expect': 'off'
  },
  settings: {
    react: {
      version: 'detect'
    }
  }
};
<commit_msg>Downgrade eslint react/prop-types rule to warning.<commit_after>module.exports = {
  env: {
    browser: true,
    es6: true,
    commonjs: true,
    node: true,
    'jest/globals': true
  },
  root: true,
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/eslint-recommended',
    'plugin:@typescript-eslint/recommended',
    'prettier/@typescript-eslint',
    'plugin:react/recommended',
    'plugin:jest/recommended'
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    project: 'tsconfig.eslint.json'
  },
  plugins: ['@typescript-eslint', 'jest'],
  rules: {
    '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
    '@typescript-eslint/naming-convention': [
      'error',
      {
        selector: 'interface',
        format: ['PascalCase'],
        custom: {
          regex: '^I[A-Z]',
          match: true
        }
      }
    ],
    '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }],
    '@typescript-eslint/no-use-before-define': 'off',
    '@typescript-eslint/camelcase': 'off',
    '@typescript-eslint/no-explicit-any': 'off',
    '@typescript-eslint/no-non-null-assertion': 'off',
    '@typescript-eslint/no-namespace': 'off',
    '@typescript-eslint/interface-name-prefix': 'off',
    '@typescript-eslint/explicit-function-return-type': 'off',
    '@typescript-eslint/ban-ts-comment': ['warn', { 'ts-ignore': true }],
    '@typescript-eslint/ban-types': 'warn',
    '@typescript-eslint/no-non-null-asserted-optional-chain': 'warn',
    '@typescript-eslint/no-var-requires': 'off',
    '@typescript-eslint/no-empty-interface': 'off',
    '@typescript-eslint/triple-slash-reference': 'warn',
    '@typescript-eslint/no-inferrable-types': 'off',
    'no-inner-declarations': 'off',
    'no-prototype-builtins': 'off',
    'no-control-regex': 'warn',
    'no-undef': 'warn',
    'no-case-declarations': 'warn',
    'no-useless-escape': 'off',
    'prefer-const': 'off',
    'jest/no-jest-import': 'off',
    'jest/no-export': 'warn',
    'jest/no-try-expect': 'warn',
    'jest/expect-expect': 'off',
    'react/prop-types': 'warn'
  },
  settings: {
    react: {
      version: 'detect'
    }
  }
};
<|endoftext|>


<commit_before>module.exports = {
    'env': {
        'browser': true,
        'commonjs': true,
        'es6': true,
        'jasmine': true
    },
    'extends': [
        'eslint:recommended',
        'plugin:flowtype/recommended'
    ],
    'globals': {
        // The globals that (1) are accessed but not defined within many of our
        // files, (2) are certainly defined, and (3) we would like to use
        // without explicitly specifying them (using a comment) inside of our
        // files.
        '__filename': false
    },
    'parser': 'babel-eslint',
    'parserOptions': {
        'ecmaFeatures': {
            'experimentalObjectRestSpread': true
        },
        'sourceType': 'module'
    },
    'plugins': [
        'flowtype',

        // ESLint's rule no-duplicate-imports does not understand Flow's import
        // type. Fortunately, eslint-plugin-import understands Flow's import
        // type.
        'import'
    ],
    'rules': {
        'new-cap': 2,
        'no-console': 0,
        'semi': [ 'error', 'always' ],

        'no-cond-assign': 2,
        'no-constant-condition': 2,
        'no-control-regex': 2,
        'no-debugger': 2,
        'no-dupe-args': 2,
        'no-duplicate-case': 2,
        'no-empty': 2,
        'no-empty-character-class': 2,
        'no-ex-assign': 2,
        'no-extra-boolean-cast': 2,
        'no-extra-parens': [
            'error',
            'all',
            { 'nestedBinaryExpressions': false }
        ],
        'no-extra-semi': 2,
        'no-func-assign': 2,

        'prefer-spread': 2,
        'require-yield': 2,
        'rest-spread-spacing': 2,
        'sort-imports': 0,
        'template-curly-spacing': 2,
        'yield-star-spacing': 2,

        'import/no-duplicates': 2
    }
};
<commit_msg>chore(eslint): Add no-inner-declarations rule<commit_after>module.exports = {
    'env': {
        'browser': true,
        'commonjs': true,
        'es6': true,
        'jasmine': true
    },
    'extends': [
        'eslint:recommended',
        'plugin:flowtype/recommended'
    ],
    'globals': {
        // The globals that (1) are accessed but not defined within many of our
        // files, (2) are certainly defined, and (3) we would like to use
        // without explicitly specifying them (using a comment) inside of our
        // files.
        '__filename': false
    },
    'parser': 'babel-eslint',
    'parserOptions': {
        'ecmaFeatures': {
            'experimentalObjectRestSpread': true
        },
        'sourceType': 'module'
    },
    'plugins': [
        'flowtype',

        // ESLint's rule no-duplicate-imports does not understand Flow's import
        // type. Fortunately, eslint-plugin-import understands Flow's import
        // type.
        'import'
    ],
    'rules': {
        'new-cap': 2,
        'no-console': 0,
        'semi': [ 'error', 'always' ],

        'no-cond-assign': 2,
        'no-constant-condition': 2,
        'no-control-regex': 2,
        'no-debugger': 2,
        'no-dupe-args': 2,
        'no-duplicate-case': 2,
        'no-empty': 2,
        'no-empty-character-class': 2,
        'no-ex-assign': 2,
        'no-extra-boolean-cast': 2,
        'no-extra-parens': [
            'error',
            'all',
            { 'nestedBinaryExpressions': false }
        ],
        'no-extra-semi': 2,
        'no-func-assign': 2,
        'no-inner-declarations': 2,

        'prefer-spread': 2,
        'require-yield': 2,
        'rest-spread-spacing': 2,
        'sort-imports': 0,
        'template-curly-spacing': 2,
        'yield-star-spacing': 2,

        'import/no-duplicates': 2
    }
};
<|endoftext|>


<commit_before>'use strict';

module.exports = {
	'env': {
		'browser': true,
		'es6': true,
		'node': true,
		'mocha': true
	},
	'globals': {
		'$': true,
		'chrome': true,
		'Require': true,
		'define': true
	},
	'rules': {
		/**
		 * Possible errors
		 */
		// Disallow assignment operators in conditional expressions
		'no-cond-assign': 'error',
		// Disallow constant expressions in conditions
		'no-constant-condition': 'error',
		// Disallow duplicate arguments in function definitions
		'no-dupe-args': 'error',
		// Disallow duplicate keys in object literals
		'no-dupe-keys': 'error',
		// Disallow duplicate case labels
		'no-duplicate-case': 'error',
		// Disallow empty block statements
		'no-empty': 'error',
		// Disallow reassigning exceptions in `catch` clauses
		'no-ex-assign': 'error',
		// Disallow unnecessary boolean casts
		'no-extra-boolean-cast': 'error',
		// Disallow unnecessary semicolons
		'no-extra-semi': 'error',
		// Disallow reassigning function declarations
		'no-func-assign': 'error',
		// Disallow calling global object properties as functions
		'no-obj-calls': 'error',
		// Disallow template literal placeholder syntax in regular string
		'no-template-curly-in-string': 'error',
		// Disallow unreachable code after return, throw, continue,
		// and break statements
		'no-unreachable': 'error',
		// Require calls to `isNaN()` when checking for `NaN`
		'no-unsafe-negation': 'error',
		// Disallow negating the left operand of relational operators
		'use-isnan': 'error',
		// Enforce comparing `typeof` expressions against valid strings
		'valid-typeof': 'error',

		/**
		 * Best practices
		 */
		// Enforce consistent brace style for all control statements
		'curly': 'error',
		// Require the use of === and !==
		'eqeqeq': 'error',
		// Disallow `else` blocks after `return` statements in `if` statements
		'no-else-return': 'error',
		// Disallow empty functions
		// 'no-empty-function': 'error',
		// Disallow assignments to native objects or read-only global variables
		'no-global-assign': 'error',
		// Disallow function declarations and expressions inside loop statements
		'no-loop-func': 'error',
		// Disallow new operators with the String, Number, and Boolean objects
		'no-new-wrappers': 'error',
		// Disallow variable redeclaration
		'no-redeclare': 'error',
		// Disallow assignments where both sides are exactly the same
		'no-self-assign': 'error',
		// Disallow redundant return statements
		'no-useless-return': 'error',

		/**
		 * Strict mode
		 */
		// Require global strict mode directive
		'strict': ['error', 'global'],

		/**
		 * Variables
		 */
		// Disallow the use of undeclared variables
		'no-undef': 'error',
		// Disallow unused variables
		'no-unused-vars': 'error',

		/**
		 * Stylistic Issues
		 */
		// Require 'one true brace style', in which the opening brace
		// of a block is placed on the same line as its corresponding
		// statement or declaration
		// 'brace-style': ['error', '1tbs'],
		// Require space after comma
		'comma-spacing': ['error', {'after': true}],
		// Require or Disallow newline at the end of files
		'eol-last': ['error', 'always'],
		// Disallow spacing between function identifiers and their invocations
		'func-call-spacing': 'error',
		// use tabs as indentation
		'indent': ['error', 'tab', {
			// enforces indentation level for case clauses in switch statements
			'SwitchCase': 1,
		}],
		// Require space after colon in object literal properties
		'key-spacing': ['error', {'afterColon': true}],
		// Require space before and after keywords
		'keyword-spacing': ['error'],
		// Require Unix line endings
		'linebreak-style': ['error', 'unix'],
		// Disallow empty block statements
		'no-empty': ['error', {'allowEmptyCatch': true}],
		// Disallow `if` statements as the only statement in `else` blocks
		'no-lonely-if': 'error',
		// Disallow mixed spaces and tabs for indentation
		'no-mixed-spaces-and-tabs': 'error',
		// Disallow multiple spaces
		'no-multi-spaces': 'error',
		// Disallow nested ternary expressions
		'no-nested-ternary': 'error',
		// Disallow trailing whitespace at the end of lines
		'no-trailing-spaces': 'error',
		// Disallow ternary operators when simpler alternatives exist
		'no-unneeded-ternary': 'error',
		// Disallow whitespace before properties
		'no-whitespace-before-property': 'error',
		// Require single quotes
		'quotes': ['error', 'single'],
		// Require space before blocks
		'space-before-blocks': ['error', 'always'],
		// Disallow spaces inside of parentheses
		'space-in-parens': 'error',
		// Require spacing around infix operators
		'space-infix-ops': 'error',
		// Require semicolon at the end of statement
		'semi': ['error', 'always'],

		/**
		 * ECMAScript 6
		 */
		// Disallow reassigning class members
		'no-class-assign': 'error',
		// Disallow modifying variables that are declared using `const`
		'no-const-assign': 'error',
		// Disallow spacing around embedded expressions of template strings
		'template-curly-spacing': 'error',

		/**
		 * Warnings
		 */
		// Disallow fallthrough of case statements
		//
		// This approach could be used as a trick, but we should warn
		// the developer to make sure they suppress warning in the code
		// and (possibly) put the explanation of fallthrougt in comments.
		'no-fallthrough': 'warn',
	}
};
<commit_msg>Update ESLint rules<commit_after>'use strict';

module.exports = {
	'env': {
		'browser': true,
		'es6': true,
		'node': true,
		'mocha': true
	},
	'globals': {
		'$': true,
		'chrome': true,
		'Require': true,
		'define': true
	},
	'rules': {
		/**
		 * Possible errors
		 */
		// Disallow assignment operators in conditional expressions
		'no-cond-assign': 'error',
		// Disallow constant expressions in conditions
		'no-constant-condition': 'error',
		// Disallow duplicate arguments in function definitions
		'no-dupe-args': 'error',
		// Disallow duplicate keys in object literals
		'no-dupe-keys': 'error',
		// Disallow duplicate case labels
		'no-duplicate-case': 'error',
		// Disallow empty block statements
		'no-empty': 'error',
		// Disallow reassigning exceptions in `catch` clauses
		'no-ex-assign': 'error',
		// Disallow unnecessary boolean casts
		'no-extra-boolean-cast': 'error',
		// Disallow unnecessary semicolons
		'no-extra-semi': 'error',
		// Disallow reassigning function declarations
		'no-func-assign': 'error',
		// Disallow calling global object properties as functions
		'no-obj-calls': 'error',
		// Disallow template literal placeholder syntax in regular string
		'no-template-curly-in-string': 'error',
		// Disallow unreachable code after return, throw, continue,
		// and break statements
		'no-unreachable': 'error',
		// Require calls to `isNaN()` when checking for `NaN`
		'no-unsafe-negation': 'error',
		// Disallow negating the left operand of relational operators
		'use-isnan': 'error',
		// Enforce valid JSDoc comments
		// 'valid-jsdoc': ['error', { 'requireReturn': false }],
		// Enforce comparing `typeof` expressions against valid strings
		'valid-typeof': 'error',

		/**
		 * Best practices
		 */
		// Enforce consistent brace style for all control statements
		'curly': 'error',
		// Require the use of === and !==
		'eqeqeq': 'error',
		// Disallow `else` blocks after `return` statements in `if` statements
		'no-else-return': 'error',
		// Disallow empty functions
		// 'no-empty-function': 'error',
		// Disallow assignments to native objects or read-only global variables
		'no-global-assign': 'error',
		// Disallow function declarations and expressions inside loop statements
		'no-loop-func': 'error',
		// Disallow new operators with the String, Number, and Boolean objects
		'no-new-wrappers': 'error',
		// Disallow variable redeclaration
		'no-redeclare': 'error',
		// Disallow assignments where both sides are exactly the same
		'no-self-assign': 'error',
		// Disallow redundant return statements
		'no-useless-return': 'error',

		/**
		 * Strict mode
		 */
		// Require global strict mode directive
		'strict': ['error', 'global'],

		/**
		 * Variables
		 */
		// Disallow deleting variables
		'no-delete-var': 'error',
		// Disallow the use of undeclared variables
		'no-undef': 'error',
		// Disallow unused variables
		'no-unused-vars': 'error',

		/**
		 * Stylistic Issues
		 */
		// Require 'one true brace style', in which the opening brace
		// of a block is placed on the same line as its corresponding
		// statement or declaration
		// 'brace-style': ['error', '1tbs'],
		// Require space after comma
		'comma-spacing': ['error', {'after': true}],
		// Require or Disallow newline at the end of files
		'eol-last': ['error', 'always'],
		// Disallow spacing between function identifiers and their invocations
		'func-call-spacing': 'error',
		// use tabs as indentation
		'indent': ['error', 'tab', {
			// enforces indentation level for case clauses in switch statements
			'SwitchCase': 1,
		}],
		// Require space after colon in object literal properties
		'key-spacing': ['error', {'afterColon': true}],
		// Require space before and after keywords
		'keyword-spacing': ['error'],
		// Require Unix line endings
		'linebreak-style': ['error', 'unix'],
		// Disallow empty block statements
		'no-empty': ['error', {'allowEmptyCatch': true}],
		// Disallow `if` statements as the only statement in `else` blocks
		'no-lonely-if': 'error',
		// Disallow mixed spaces and tabs for indentation
		'no-mixed-spaces-and-tabs': 'error',
		// Disallow multiple spaces
		'no-multi-spaces': 'error',
		// Disallow nested ternary expressions
		'no-nested-ternary': 'error',
		// Disallow trailing whitespace at the end of lines
		'no-trailing-spaces': 'error',
		// Disallow ternary operators when simpler alternatives exist
		'no-unneeded-ternary': 'error',
		// Disallow whitespace before properties
		'no-whitespace-before-property': 'error',
		// Require single quotes
		'quotes': ['error', 'single'],
		// Require space before blocks
		'space-before-blocks': ['error', 'always'],
		// Disallow spaces inside of parentheses
		'space-in-parens': 'error',
		// Require spacing around infix operators
		'space-infix-ops': 'error',
		// Require semicolon at the end of statement
		'semi': ['error', 'always'],

		/**
		 * ECMAScript 6
		 */
		// Disallow reassigning class members
		'no-class-assign': 'error',
		// Disallow modifying variables that are declared using `const`
		'no-const-assign': 'error',
		// Disallow duplicate class members
		'no-dupe-class-members': 'error',
		// Disallow `this` or `super` before calling super() in constructors
		'no-this-before-super': 'error',
		// Disallow spacing around embedded expressions of template strings
		'template-curly-spacing': 'error',

		/**
		 * Warnings
		 */
		// Disallow fallthrough of case statements
		//
		// This approach could be used as a trick, but we should warn
		// the developer to make sure they suppress warning in the code
		// and (possibly) put the explanation of fallthrougt in comments.
		'no-fallthrough': 'warn',
	}
};
<|endoftext|>


<commit_before>module.exports = {
  root: true,
  parser: 'babel-eslint',
  parserOptions: {
    ecmaVersion: 2018,
    sourceType: 'module',
    ecmaFeatures: {
      legacyDecorators: true
    }
  },
  plugins: ['ember'],
  extends: [
    'plugin:ember/recommended'
  ],
  env: {
    'browser': true
  },
  rules: {
    'ember/use-ember-data-rfc-395-imports': false,
    'strict': 0,
    'no-unused-vars': 'error',
    'no-dupe-keys': 'error'
  },
  overrides: [
    // node files
    {
      files: [
        '.eslintrc.js',
        '.template-lintrc.js',
        'ember-cli-build.js',
        'index.js',
        'testem.js',
        'blueprints/*/index.js',
        'config/**/*.js',
        'tests/dummy/config/**/*.js'
      ],
      excludedFiles: [
        'addon/**',
        'addon-test-support/**',
        'app/**',
        'tests/dummy/app/**'
      ],
      parserOptions: {
        sourceType: 'script'
      },
      env: {
        browser: false,
        node: true
      },
      plugins: ['node'],
      rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, {
        // add your custom rules and overrides for node files here
      })
    }
  ]
};
<commit_msg>Fix ESLint config to ignore Ember Data imports RFC rule<commit_after>module.exports = {
  root: true,
  parser: 'babel-eslint',
  parserOptions: {
    ecmaVersion: 2018,
    sourceType: 'module',
    ecmaFeatures: {
      legacyDecorators: true
    }
  },
  plugins: ['ember'],
  extends: [
    'plugin:ember/recommended'
  ],
  env: {
    'browser': true
  },
  rules: {
    'ember/use-ember-data-rfc-395-imports': 0,
    'strict': 0,
    'no-unused-vars': 'error',
    'no-dupe-keys': 'error'
  },
  overrides: [
    // node files
    {
      files: [
        '.eslintrc.js',
        '.template-lintrc.js',
        'ember-cli-build.js',
        'index.js',
        'testem.js',
        'blueprints/*/index.js',
        'config/**/*.js',
        'tests/dummy/config/**/*.js'
      ],
      excludedFiles: [
        'addon/**',
        'addon-test-support/**',
        'app/**',
        'tests/dummy/app/**'
      ],
      parserOptions: {
        sourceType: 'script'
      },
      env: {
        browser: false,
        node: true
      },
      plugins: ['node'],
      rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, {
        // add your custom rules and overrides for node files here
      })
    }
  ]
};
<|endoftext|>


<commit_before>import * as Events from './Events'

/**
 * An interface for interacting with a Chatternet channel. A channel is simply
 * a stream of Chatternet events.
 */
class Channel {
  /**
   * @callback Channel#onReceiveCallback
   * @param {Message} message - The message that was received.
   */

  /**
   * @param {!string} id - Identifier of the channel.
   * @param {!Chatternet} chatternet - Scope for channel interactions.
   */
  constructor({id, chatternet}) {
    this.id = id
    this._chatternet = chatternet
    this._onReceive = null
  }

  /**
   * @param {Channel#onReceiveCallback} callback - Called when a new message is
   *     received from another identity. Note: {Chatternet.identity}'s own
   *     messages do not invoke this callback.
   */
  onReceive(callback) {
    this._onReceive = callback
    this._chatternet.on(Events.UPDATE_MESSAGES, (channelId, messages) => {
      if (channelId !== this.id) return
      messages.forEach(this._onReceive)
    })
  }

  /**
   * Sends the given message from {Chatternet.identity} to the channel.
   * @param {Message} message - The message to send. Note that
   *     {Message.identity} is replaced with {Chatternet.identity}.
   */
  send(message) {
    this._chatternet.emit(Events.ADD_MESSAGE, this.id, Object.assign({}, message, {identityId: this._chatternet.identity}))
  }
}

export default Channel
<commit_msg>Fix onReceive's message filtering.<commit_after>import * as Events from './Events'

/**
 * An interface for interacting with a Chatternet channel. A channel is simply
 * a stream of Chatternet events.
 */
class Channel {
  /**
   * @callback Channel#onReceiveCallback
   * @param {Message} message - The message that was received.
   */

  /**
   * @param {!string} id - Identifier of the channel.
   * @param {!Chatternet} chatternet - Scope for channel interactions.
   */
  constructor({id, chatternet}) {
    this.id = id
    this._chatternet = chatternet
    this._onReceive = null
  }

  /**
   * @param {Channel#onReceiveCallback} callback - Called when a new message is
   *     received from another identity. Note: {Chatternet.identity}'s own
   *     messages do not invoke this callback.
   */
  onReceive(callback) {
    this._onReceive = callback
    this._chatternet.on(Events.UPDATE_MESSAGES, (channelId, messages) => {
      messages.filter(m => m.identityId !== this._chatternet.identity).forEach(this._onReceive)
    })
  }

  /**
   * Sends the given message from {Chatternet.identity} to the channel.
   * @param {Message} message - The message to send. Note that
   *     {Message.identity} is replaced with {Chatternet.identity}.
   */
  send(message) {
    this._chatternet.emit(Events.ADD_MESSAGE, this.id, Object.assign({}, message, {identityId: this._chatternet.identity}))
  }
}

export default Channel
<|endoftext|>


<commit_before>'use strict';

const _ = require('lodash');
const path = require('path');
const tls = require('tls');
const cheerio = require('cheerio');
const Crawler = require('simplecrawler');
const URL = require('url');
const cursor = require('ansi')(process.stdout);

const LoggerFactory = require('./Logger');
const ErrorHandlerFactory = require('./ErrorHandler');
const HashProcessorFactory = require('./HashProcessor');
const configUtil = require('./config/configUtil')();

const mailtoMatcher = /mailto:/i;

let spooledReportTimer;

/**
 * Validate the seed url is well formed.
 *
 * @param {String} crawlerSeedUrl the crawler seed url.
 */
function validateUrl(crawlerSeedUrl) {
  let urlProtocol;

  if (!crawlerSeedUrl || !crawlerSeedUrl.protocol || !crawlerSeedUrl.hostname || !crawlerSeedUrl.path) {
    throw new Error('Invalid URL.');
  }

  // Strip off trailing colon
  urlProtocol = crawlerSeedUrl.protocol.slice(0, -1);
}

/**
 * Set the config.rootUrl, for use in later functions.
 *
 * @param {object} config the spider config.
 * @param {String} crawlerSeedUrl the crawler seed url.
 */
function setConfigRootUrl(config, crawlerSeedUrl) {
  let portString = '';
  let protocolString = crawlerSeedUrl.protocol || 'https';

  if (crawlerSeedUrl.port) {
    portString = `:${crawlerSeedUrl.port}`;
  }

  config.rootUrl = `${protocolString}://${crawlerSeedUrl.hostname}${portString}`;
  cursor.reset().write(`Set config.rootUrl to ${config.rootUrl}\n`);
}

/**
 * Set the TLS settings based on the config.
 *
 * @param {object} config the spider config.
 */
function initTls(config) {
  // Deal with ciphers.
  if (!config.strictCiphers) {
    tls.DEFAULT_CIPHERS = 'ALL';
    cursor.reset().write('Relaxing cipher list to allow any cipher.\n');
  }

  // Ignore invalid SSL certs.
  if (config.simplecrawlerConfig.ignoreInvalidSSL) {
    process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0;
    cursor.reset().write('Ignoring invalid certificates.\n');
  }
}

/**
 * Initialize the spool, if specified.
 *
 * @param {object} config the spider config.
 * @param {object} crawler the simplecrawler object.
 */
function initSpool(config, crawler) {
  // Start the spooled report timer.
  if (config.reportSpoolInterval > 0) {
    cursor.reset().write(`Starting spool reporting every ${config.reportSpoolInterval} ms.\n`);
    spooledReportTimer = setInterval(() => {
      crawler.queue.filterItems({ status: 'spooled' }, (error, spooled) => {
        cursor.reset().write('currently spooled:\n');
        _.forEach(spooled, (item) => {
          cursor.reset().write(`  ${item.url}\n`);
        });
        cursor.reset().write('\n');
      });
    }, config.reportSpoolInterval);
  }
}

/**
 * Add fetch conditions based on `excludePatterns` property.
 *
 * @param {object} config the spider config.
 * @param {object} crawler the simplecrawler object.
 * @param {object} logger the spider logger.
 */
function initExcludePatterns(config, crawler, logger) {
  if (config.excludePatterns && config.excludePatterns.length > 0) {
    crawler.addFetchCondition((parsedURL) => {
      const firstMatchingPattern = configUtil.getFirstMatchingPattern(
        config.excludePatterns,
        parsedURL.url,
        config.rootUrl
      );

      if (firstMatchingPattern) {
        logger.logIgnore(parsedURL);
      }

      return !firstMatchingPattern;
    });
  }
}

/**
 * Add fetch conditions based on `includePatterns` property.
 *
 * @param {object} config the spider config.
 * @param {object} crawler the simplecrawler object.
 * @param {object} logger the spider logger.
 */
function initIncludePatterns(config, crawler, logger) {
  if (config.includePatterns && config.includePatterns.length > 0) {
    crawler.addFetchCondition((parsedURL) => {
      const firstMatchingPattern = configUtil.getFirstMatchingPattern(
        config.includePatterns,
        parsedURL.url,
        config.rootUrl
      );

      if (!firstMatchingPattern) {
        logger.logIgnore(parsedURL);
      }

      return !!firstMatchingPattern;
    });
  }
}

/**
 * Inject simplecrawler config into the crawler instance.
 *
 * @param {object} config the spider config.
 * @param {object} crawler the simplecrawler object.
 */
function injectCrawlerConfig(config, crawler) {
  _.forEach(config.simplecrawlerConfig, (value, key) => {
    crawler[key] = value;
  });
}

/**
 * Add any config.additionalPaths to the crawler queue.
 *
 * @param {object} config the spider config.
 * @param {object} crawler the simplecrawler object.
 */
function initAdditionalPaths(config, crawler) {
  if (config.additionalPaths) {
    _.forEach(config.additionalPaths, (relativePath) => {
      crawler.queueURL(relativePath, undefined);
    });
  }
}

/**
 * Check whether the title of the page (if present) matches the given pattern.
 *
 * @param {String} url the url
 * @param {String} pattern the pattern
 * @param {object} $ the Cheerio document
 * @param {object} errorHandler the errorHandler where to send errors.
 */
function checkTitle(url, pattern, $, errorHandler) {
  $('title').each(function () {
    const titleText = $(this).text();

    if (!new RegExp(pattern, 'g').test(titleText)) {
      errorHandler.handleTitleError({
        url,
        msg: `pattern: ${pattern} failed on title: ${titleText}`
      });
    }
  });
}

/**
 * Set up the crawler discover resources to parse and validate any html pages using cheerio.
 *
 * @param {object} config the spider config.
 * @param {object} crawler the simplecrawler object.
 * @param {object} errorHandler the errorHandler instance.
 * @param {object} hashProcessor the hashProcessor instance.
 */
function initDiscoverResources(config, crawler, errorHandler, hashProcessor) {
  // Prevent analyzing any resource outside our domain of interest.
  // Use cheerio to prune certain blocks before we scan for resources.
  // Also, remove all query strings and hashes to ensure uniformity.
  crawler.oldDiscoverResources = crawler.discoverResources;
  crawler.discoverResources = function (buf, queueItem) {
    let $;
    let list;
    let strContent;

    // Special processing for HTML
    if (queueItem.stateData.contentType.indexOf('text/html') === 0) {
      // Load the contents into Cheerio to tease apart the DOM
      // Do not parse 'URL(s)', which is a valid content string (who knew?)
      strContent = buf.toString('utf-8');
      strContent = strContent.replace(/URL\(s\)/g, '');
      $ = cheerio.load(strContent);

      // Find ids and anchor hashes and save them off! These are potential hash targets.
      hashProcessor.findSupportedHashes(queueItem.url, $);

      // If this is the host we're crawling, parse the page.
      if (queueItem.host === crawler.host) {
        if (config.titlePattern) {
          // Check the title matches the given pattern.
          checkTitle(queueItem.url, config.titlePattern, $, errorHandler);
        }

        // Do not dive into <pre>, <code>, or <svg> tags looking for resources.
        $('pre,code,svg').remove();

        // Save off expected hashes for the hrefs. Simplecrawler throws hashes away by default.
        $('a[href]').each(function () {
          const href = $(this).attr('href');

          hashProcessor.registerExpectedHash(href, queueItem);
        });

        // Go discover the embedded resources.
        list = this.oldDiscoverResources($.html(), queueItem);

        // Process URLs to remove mailto's.
        list = _.map(list, (considerURL) => {
          if (mailtoMatcher.test(considerURL)) {
            return null;
          }

          return considerURL;
        });

        // Return only unique items in the list.
        // Shuffle them, because:
        // 1. Shuffling improves throughput, since we'll hit more servers simultaneously
        // 2. Shuffling decreases likelihood we are seen as a robot by servers.
        // 3. Shuffling reduces the chance of missing a race condition error over time.
        return _.shuffle(_.uniq(list));
      }
    }

    return [];
  };
}

/**
 * Set up the other fetch and queue error handlers.
 *
 * @param {object} crawler the simplecrawler object.
 * @param {object} errorHandler the errorHandler instance.
 */
function initErrorHandlers(crawler, errorHandler) {
  // Handle fetch errors.
  crawler.on('fetch404', (queueItem) => {
    errorHandler.handleFetchServerError(404, queueItem);
  });
  crawler.on('fetcherror', (queueItem) => {
    errorHandler.handleFetchError(queueItem, 'fetcherror');
  });
  crawler.on('fetchdataerror', (queueItem) => {
    errorHandler.handleFetchError(queueItem, 'fetchdataerror');
  });
  crawler.on('gziperror', (queueItem) => {
    errorHandler.handleFetchError(queueItem, 'gziperror');
  });
  crawler.on('fetchtimeout', (queueItem) => {
    errorHandler.handleFetchError(queueItem, 'fetchtimeout');
  });

  // Handle client errors, which are a different breed altogether.
  crawler.on('fetchclienterror', (queueItem, error) => {
    errorHandler.handleFetchClientError(queueItem, 'fetchclienterror', error);
  });

  // Handle non-fetch queue error.
  crawler.on('queueerror', (error, URLData) => {
    errorHandler.handleQueueError(error, URLData);
  });
}

module.exports = function (myUrl, config, verbose) {
  return {
    crawl() {
      let crawler;
      let logger;
      let errorHandler;
      let hashProcessor;

      const crawlerSeedUrl = URL.parse(myUrl);

      validateUrl(crawlerSeedUrl);
      setConfigRootUrl(config, crawlerSeedUrl);
      initTls(config);

      crawler = new Crawler(myUrl);

      initAdditionalPaths(config, crawler);
      initSpool(config, crawler);
      injectCrawlerConfig(config, crawler);

      logger = new LoggerFactory(crawler, verbose);
      errorHandler = new ErrorHandlerFactory(config, logger);
      hashProcessor = new HashProcessorFactory(crawler, errorHandler, logger);

      initExcludePatterns(config, crawler, logger);
      initIncludePatterns(config, crawler, logger);

      initDiscoverResources(config, crawler, errorHandler, hashProcessor);
      initErrorHandlers(crawler, errorHandler);

      // This is a success callback.
      crawler.on('fetchcomplete', (queueItem) => {
        logger.logSuccess(queueItem);
      });

      // Capture links.
      crawler.on('discoverycomplete', (queueItem, resources) => {
        _.forEach(resources, (url) => {
          logger.registerLink(queueItem.url, url);
        });
      });

      // Following a redirect.
      crawler.on('fetchredirect', (queueItem, redirectURL) => {
        logger.logRedirect(queueItem, redirectURL);
      });

      // We're done.  Log a summary and exit.
      crawler.on('complete', () => {
        let exitCode;

        // Stop the spool report timer.
        if (spooledReportTimer) {
          clearInterval(spooledReportTimer);
        }

        // Find and log errors associated to hashes.
        hashProcessor.findErrors();

        // Log and finish.
        // Wait 2 seconds to exit to allow console logs a last chance to make it out.
        exitCode = logger.report();
        setTimeout(() => {
          process.exit(exitCode);
        }, 2000);
      });

      crawler.start();
    }
  };
};
<commit_msg>Fix a bug with the protocol requiring a colon<commit_after>'use strict';

const _ = require('lodash');
const path = require('path');
const tls = require('tls');
const cheerio = require('cheerio');
const Crawler = require('simplecrawler');
const URL = require('url');
const cursor = require('ansi')(process.stdout);

const LoggerFactory = require('./Logger');
const ErrorHandlerFactory = require('./ErrorHandler');
const HashProcessorFactory = require('./HashProcessor');
const configUtil = require('./config/configUtil')();

const mailtoMatcher = /mailto:/i;

let spooledReportTimer;

/**
 * Validate the seed url is well formed.
 *
 * @param {String} crawlerSeedUrl the crawler seed url.
 */
function validateUrl(crawlerSeedUrl) {
  let urlProtocol;

  if (!crawlerSeedUrl || !crawlerSeedUrl.protocol || !crawlerSeedUrl.hostname || !crawlerSeedUrl.path) {
    throw new Error('Invalid URL.');
  }
}

/**
 * Set the config.rootUrl, for use in later functions.
 *
 * @param {object} config the spider config.
 * @param {String} crawlerSeedUrl the crawler seed url.
 */
function setConfigRootUrl(config, crawlerSeedUrl) {
  let portString = '';

  // yes, the protocol includes the trailing colon in simplecrawler.
  let protocolString = crawlerSeedUrl.protocol || 'https:';

  if (crawlerSeedUrl.port) {
    portString = `:${crawlerSeedUrl.port}`;
  }

  config.rootUrl = `${protocolString}//${crawlerSeedUrl.hostname}${portString}`;
  cursor.reset().write(`Set config.rootUrl to ${config.rootUrl}\n`);
}

/**
 * Set the TLS settings based on the config.
 *
 * @param {object} config the spider config.
 */
function initTls(config) {
  // Deal with ciphers.
  if (!config.strictCiphers) {
    tls.DEFAULT_CIPHERS = 'ALL';
    cursor.reset().write('Relaxing cipher list to allow any cipher.\n');
  }

  // Ignore invalid SSL certs.
  if (config.simplecrawlerConfig.ignoreInvalidSSL) {
    process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0;
    cursor.reset().write('Ignoring invalid certificates.\n');
  }
}

/**
 * Initialize the spool, if specified.
 *
 * @param {object} config the spider config.
 * @param {object} crawler the simplecrawler object.
 */
function initSpool(config, crawler) {
  // Start the spooled report timer.
  if (config.reportSpoolInterval > 0) {
    cursor.reset().write(`Starting spool reporting every ${config.reportSpoolInterval} ms.\n`);
    spooledReportTimer = setInterval(() => {
      crawler.queue.filterItems({ status: 'spooled' }, (error, spooled) => {
        cursor.reset().write('currently spooled:\n');
        _.forEach(spooled, (item) => {
          cursor.reset().write(`  ${item.url}\n`);
        });
        cursor.reset().write('\n');
      });
    }, config.reportSpoolInterval);
  }
}

/**
 * Add fetch conditions based on `excludePatterns` property.
 *
 * @param {object} config the spider config.
 * @param {object} crawler the simplecrawler object.
 * @param {object} logger the spider logger.
 */
function initExcludePatterns(config, crawler, logger) {
  if (config.excludePatterns && config.excludePatterns.length > 0) {
    crawler.addFetchCondition((parsedURL) => {
      const firstMatchingPattern = configUtil.getFirstMatchingPattern(
        config.excludePatterns,
        parsedURL.url,
        config.rootUrl
      );

      if (firstMatchingPattern) {
        logger.logIgnore(parsedURL);
      }

      return !firstMatchingPattern;
    });
  }
}

/**
 * Add fetch conditions based on `includePatterns` property.
 *
 * @param {object} config the spider config.
 * @param {object} crawler the simplecrawler object.
 * @param {object} logger the spider logger.
 */
function initIncludePatterns(config, crawler, logger) {
  if (config.includePatterns && config.includePatterns.length > 0) {
    crawler.addFetchCondition((parsedURL) => {
      const firstMatchingPattern = configUtil.getFirstMatchingPattern(
        config.includePatterns,
        parsedURL.url,
        config.rootUrl
      );

      if (!firstMatchingPattern) {
        logger.logIgnore(parsedURL);
      }

      return !!firstMatchingPattern;
    });
  }
}

/**
 * Inject simplecrawler config into the crawler instance.
 *
 * @param {object} config the spider config.
 * @param {object} crawler the simplecrawler object.
 */
function injectCrawlerConfig(config, crawler) {
  _.forEach(config.simplecrawlerConfig, (value, key) => {
    crawler[key] = value;
  });
}

/**
 * Add any config.additionalPaths to the crawler queue.
 *
 * @param {object} config the spider config.
 * @param {object} crawler the simplecrawler object.
 */
function initAdditionalPaths(config, crawler) {
  if (config.additionalPaths) {
    _.forEach(config.additionalPaths, (relativePath) => {
      crawler.queueURL(relativePath, undefined);
    });
  }
}

/**
 * Check whether the title of the page (if present) matches the given pattern.
 *
 * @param {String} url the url
 * @param {String} pattern the pattern
 * @param {object} $ the Cheerio document
 * @param {object} errorHandler the errorHandler where to send errors.
 */
function checkTitle(url, pattern, $, errorHandler) {
  $('title').each(function () {
    const titleText = $(this).text();

    if (!new RegExp(pattern, 'g').test(titleText)) {
      errorHandler.handleTitleError({
        url,
        msg: `pattern: ${pattern} failed on title: ${titleText}`
      });
    }
  });
}

/**
 * Set up the crawler discover resources to parse and validate any html pages using cheerio.
 *
 * @param {object} config the spider config.
 * @param {object} crawler the simplecrawler object.
 * @param {object} errorHandler the errorHandler instance.
 * @param {object} hashProcessor the hashProcessor instance.
 */
function initDiscoverResources(config, crawler, errorHandler, hashProcessor) {
  // Prevent analyzing any resource outside our domain of interest.
  // Use cheerio to prune certain blocks before we scan for resources.
  // Also, remove all query strings and hashes to ensure uniformity.
  crawler.oldDiscoverResources = crawler.discoverResources;
  crawler.discoverResources = function (buf, queueItem) {
    let $;
    let list;
    let strContent;

    // Special processing for HTML
    if (queueItem.stateData.contentType.indexOf('text/html') === 0) {
      // Load the contents into Cheerio to tease apart the DOM
      // Do not parse 'URL(s)', which is a valid content string (who knew?)
      strContent = buf.toString('utf-8');
      strContent = strContent.replace(/URL\(s\)/g, '');
      $ = cheerio.load(strContent);

      // Find ids and anchor hashes and save them off! These are potential hash targets.
      hashProcessor.findSupportedHashes(queueItem.url, $);

      // If this is the host we're crawling, parse the page.
      if (queueItem.host === crawler.host) {
        if (config.titlePattern) {
          // Check the title matches the given pattern.
          checkTitle(queueItem.url, config.titlePattern, $, errorHandler);
        }

        // Do not dive into <pre>, <code>, or <svg> tags looking for resources.
        $('pre,code,svg').remove();

        // Save off expected hashes for the hrefs. Simplecrawler throws hashes away by default.
        $('a[href]').each(function () {
          const href = $(this).attr('href');

          hashProcessor.registerExpectedHash(href, queueItem);
        });

        // Go discover the embedded resources.
        list = this.oldDiscoverResources($.html(), queueItem);

        // Process URLs to remove mailto's.
        list = _.map(list, (considerURL) => {
          if (mailtoMatcher.test(considerURL)) {
            return null;
          }

          return considerURL;
        });

        // Return only unique items in the list.
        // Shuffle them, because:
        // 1. Shuffling improves throughput, since we'll hit more servers simultaneously
        // 2. Shuffling decreases likelihood we are seen as a robot by servers.
        // 3. Shuffling reduces the chance of missing a race condition error over time.
        return _.shuffle(_.uniq(list));
      }
    }

    return [];
  };
}

/**
 * Set up the other fetch and queue error handlers.
 *
 * @param {object} crawler the simplecrawler object.
 * @param {object} errorHandler the errorHandler instance.
 */
function initErrorHandlers(crawler, errorHandler) {
  // Handle fetch errors.
  crawler.on('fetch404', (queueItem) => {
    errorHandler.handleFetchServerError(404, queueItem);
  });
  crawler.on('fetcherror', (queueItem) => {
    errorHandler.handleFetchError(queueItem, 'fetcherror');
  });
  crawler.on('fetchdataerror', (queueItem) => {
    errorHandler.handleFetchError(queueItem, 'fetchdataerror');
  });
  crawler.on('gziperror', (queueItem) => {
    errorHandler.handleFetchError(queueItem, 'gziperror');
  });
  crawler.on('fetchtimeout', (queueItem) => {
    errorHandler.handleFetchError(queueItem, 'fetchtimeout');
  });

  // Handle client errors, which are a different breed altogether.
  crawler.on('fetchclienterror', (queueItem, error) => {
    errorHandler.handleFetchClientError(queueItem, 'fetchclienterror', error);
  });

  // Handle non-fetch queue error.
  crawler.on('queueerror', (error, URLData) => {
    errorHandler.handleQueueError(error, URLData);
  });
}

module.exports = function (myUrl, config, verbose) {
  return {
    crawl() {
      let crawler;
      let logger;
      let errorHandler;
      let hashProcessor;

      const crawlerSeedUrl = URL.parse(myUrl);

      validateUrl(crawlerSeedUrl);
      setConfigRootUrl(config, crawlerSeedUrl);
      initTls(config);

      crawler = new Crawler(myUrl);

      initAdditionalPaths(config, crawler);
      initSpool(config, crawler);
      injectCrawlerConfig(config, crawler);

      logger = new LoggerFactory(crawler, verbose);
      errorHandler = new ErrorHandlerFactory(config, logger);
      hashProcessor = new HashProcessorFactory(crawler, errorHandler, logger);

      initExcludePatterns(config, crawler, logger);
      initIncludePatterns(config, crawler, logger);

      initDiscoverResources(config, crawler, errorHandler, hashProcessor);
      initErrorHandlers(crawler, errorHandler);

      // This is a success callback.
      crawler.on('fetchcomplete', (queueItem) => {
        logger.logSuccess(queueItem);
      });

      // Capture links.
      crawler.on('discoverycomplete', (queueItem, resources) => {
        _.forEach(resources, (url) => {
          logger.registerLink(queueItem.url, url);
        });
      });

      // Following a redirect.
      crawler.on('fetchredirect', (queueItem, redirectURL) => {
        logger.logRedirect(queueItem, redirectURL);
      });

      // We're done.  Log a summary and exit.
      crawler.on('complete', () => {
        let exitCode;

        // Stop the spool report timer.
        if (spooledReportTimer) {
          clearInterval(spooledReportTimer);
        }

        // Find and log errors associated to hashes.
        hashProcessor.findErrors();

        // Log and finish.
        // Wait 2 seconds to exit to allow console logs a last chance to make it out.
        exitCode = logger.report();
        setTimeout(() => {
          process.exit(exitCode);
        }, 2000);
      });

      crawler.start();
    }
  };
};
<|endoftext|>


<commit_before>'use babel';

import { observable, autorun, computed, action } from 'mobx';
import FileStore from './stores/FileStore';
import GitStore from './stores/GitStore';
import Settings from './Settings';
import Project from './models/Project';

class Manager {
  @observable projects = [];

  /**
   * Create or Update a project.
   *
   * Props coming from file goes before any other source.
   */
  @action addProject(props) {
    const foundProject = this.projects.find(project =>
      project.rootPath.toLowerCase() === props.paths[0].toLowerCase()
    );

    if (!foundProject) {
      const newProject = new Project(props);
      this.projects.push(newProject);
    } else {
      if (foundProject.source === 'file' && props.source === 'file') {
        foundProject.updateProps(props);
      }

      if (props.source === 'file') {
        foundProject.updateProps(props);
      }
    }
  }

  constructor() {
    this.gitStore = new GitStore();
    this.fileStore = new FileStore();
    this.settings = new Settings();

    this.fileStore.fetch();

    if (atom.config.get('project-manager.includeGitRepositories')) {
      this.gitStore.fetch();
    }

    atom.config.observe('project-manager.includeGitRepositories', (include) => {
      if (include) {
        this.gitStore.fetch();
      } else {
        this.gitStore.empty();
      }
    });

    autorun(() => {
      for (const fileProp of this.fileStore.data) {
        this.addProject(fileProp);
      }

      for (const gitProp of this.gitStore.data) {
        this.addProject(gitProp);
      }
    });

    autorun(() => {
      if (this.activeProject) {
        // console.log('Active project', this.activeProject.title);
      }
    });

    atom.project.onDidChangePaths(() => {
      const activePaths = atom.project.getPaths();

      if (this.activeProject && this.activeProject.rootPath === activePaths[0]) {
        this.activeProject.updateProps({ paths: activePaths });
        this.saveProjects();
      }
    });
  }

  @computed get activeProject() {
    const activePaths = atom.project.getPaths();
    return this.projects.find(project => project.rootPath === activePaths[0]);
  }

  loadProject(project) {
    this.settings.load(project.props.settings);
  }

  open(project) {
    const win = atom.getCurrentWindow();
    const closeCurrent = atom.config.get('project-manager.closeCurrent');
    const { paths, devMode } = project.getProps();

    atom.open({
      paths,
      devMode,
      newWindow: closeCurrent,
    });

    if (closeCurrent) {
      setTimeout(() => win.close(), 0);
    }
  }

  saveProject(props) {
    this.addProject(props);
    this.saveProjects();
  }

  saveProjects() {
    const projects = this.projects.filter(project => project.props.source === 'file');
    const arr = [];

    for (const project of projects) {
      arr.push(project.getProps());
    }

    this.fileStore.store(arr);
  }
}

const manager = new Manager();
export default manager;
<commit_msg>Load project settings<commit_after>'use babel';

import { observable, autorun, computed, action } from 'mobx';
import FileStore from './stores/FileStore';
import GitStore from './stores/GitStore';
import Settings from './Settings';
import Project from './models/Project';

class Manager {
  @observable projects = [];

  /**
   * Create or Update a project.
   *
   * Props coming from file goes before any other source.
   */
  @action addProject(props) {
    const foundProject = this.projects.find(project =>
      project.rootPath.toLowerCase() === props.paths[0].toLowerCase()
    );

    if (!foundProject) {
      const newProject = new Project(props);
      this.projects.push(newProject);
    } else {
      if (foundProject.source === 'file' && props.source === 'file') {
        foundProject.updateProps(props);
      }

      if (props.source === 'file') {
        foundProject.updateProps(props);
      }
    }
  }

  constructor() {
    this.gitStore = new GitStore();
    this.fileStore = new FileStore();
    this.settings = new Settings();

    this.fileStore.fetch();

    if (atom.config.get('project-manager.includeGitRepositories')) {
      this.gitStore.fetch();
    }

    atom.config.observe('project-manager.includeGitRepositories', (include) => {
      if (include) {
        this.gitStore.fetch();
      } else {
        this.gitStore.empty();
      }
    });

    autorun(() => {
      for (const fileProp of this.fileStore.data) {
        this.addProject(fileProp);
      }

      for (const gitProp of this.gitStore.data) {
        this.addProject(gitProp);
      }
    });

    autorun(() => {
      if (this.activeProject) {
        this.loadProject(this.activeProject);
      }
    });

    atom.project.onDidChangePaths(() => {
      const activePaths = atom.project.getPaths();

      if (this.activeProject && this.activeProject.rootPath === activePaths[0]) {
        this.activeProject.updateProps({ paths: activePaths });
        this.saveProjects();
      }
    });
  }

  @computed get activeProject() {
    const activePaths = atom.project.getPaths();
    return this.projects.find(project => project.rootPath === activePaths[0]);
  }

  loadProject(project) {
    this.settings.load(project.props.settings);
  }

  open(project) {
    const win = atom.getCurrentWindow();
    const closeCurrent = atom.config.get('project-manager.closeCurrent');
    const { paths, devMode } = project.getProps();

    atom.open({
      paths,
      devMode,
      newWindow: closeCurrent,
    });

    if (closeCurrent) {
      setTimeout(() => win.close(), 0);
    }
  }

  saveProject(props) {
    this.addProject(props);
    this.saveProjects();
  }

  saveProjects() {
    const projects = this.projects.filter(project => project.props.source === 'file');
    const arr = [];

    for (const project of projects) {
      arr.push(project.getProps());
    }

    this.fileStore.store(arr);
  }
}

const manager = new Manager();
export default manager;
<|endoftext|>


<commit_before>/* cache.js, part of the poŝto suite.
 *
 * Copyright (c) Paul Tagliamonte <tag@pault.ag>, 2012 under the terms and
 * conditions of the Expat license, a copy of which you should have recieved
 * with this application. */

var util = require('util'),
  events = require('events');

/**
 * Cache Layout (etc):
 *
 *  cache/
 *    boxen/
 *      INBOX/    (folder)
 *        193848/ (validity)
 *          23    (ID)
 *           +--> Symbolic Link to real header
 *  headers/
 *    a/
 *      b/
 *        c/
 *          abc123@foo.domain.tld.json
 *
 *  content/
 *    a/
 *      b/
 *        c/
 *          abc123@foo.domain.tld/
 *            part1
 *            part2
 *            part2.1
 */

/**
 * Base Constructor.
 */
PoshtoCache = function( settings ) {
  if ( ! ( this instanceof PoshtoCache ) ) {
    return new PoshtoCache( settings );
  }
}

/* We'll set up the inherit from events */
util.inherits( PoshtoCache, events.EventEmitter );

PoshtoCache.prototype.get_headers = function(message_id) {
}

exports.PoshtoCache = PoshtoCache;

// vim: tabstop=2 expandtab shiftwidth=2 softtabstop=2
<commit_msg>diagram was slightly off :)<commit_after>/* cache.js, part of the poŝto suite.
 *
 * Copyright (c) Paul Tagliamonte <tag@pault.ag>, 2012 under the terms and
 * conditions of the Expat license, a copy of which you should have recieved
 * with this application. */

var util = require('util'),
  events = require('events');

/**
 * Cache Layout (etc):
 *
 *  cache/
 *    boxen/
 *      INBOX/    (folder)
 *        193848/ (validity)
 *          23    (ID)
 *           +--> Symbolic Link to real header
 *    headers/
 *      a/
 *        b/
 *          c/
 *            abc123@foo.domain.tld.json
 *
 *    content/
 *      a/
 *        b/
 *          c/
 *            abc123@foo.domain.tld/
 *              part1
 *              part2
 *              part2.1
 */

/**
 * Base Constructor.
 */
PoshtoCache = function( settings ) {
  if ( ! ( this instanceof PoshtoCache ) ) {
    return new PoshtoCache( settings );
  }
}

/* We'll set up the inherit from events */
util.inherits( PoshtoCache, events.EventEmitter );

PoshtoCache.prototype.get_headers = function(message_id) {
}

exports.PoshtoCache = PoshtoCache;

// vim: tabstop=2 expandtab shiftwidth=2 softtabstop=2
<|endoftext|>


<commit_before>//-------------------------------------------------===------------------------------------------------------
//                                                Const
//-------------------------------------------------===------------------------------------------------------

//Containers
var CONTAINER_TAG_CLASSES_MAP = {
    'container': 'container',
    'container-fluid': 'container-fluid'
};

//Clearfix
var CLEARFIX_TAG =
    CLEARFIX_CLASS = 'clearfix';

//Columns
var COL_TAG = 'c',
    SIZE_CLASS_REGEXP = /^col-(xs|sm|md|lg)-([1-9]|1[0-2])$/,
    NUMBERED_COL_TAG_REGEXP = /^c([1-9]|1[0-2])$/,
    NUMBERED_COL_CLASS_TEMPLATE = 'col-md-{size}',
    DEFAULT_COL_SIZE_CLASS = 'col-md-1',
    IMPLIED_COL_SIZE_CLASS = 'col-md-12';

//Row
var ROW_TAG = 'r',
    ROW_CLASS = 'row',
    IMPLIED_COL_START_TAG_TOKEN = {
        tagName: 'div',
        selfClosing: false,
        attrs: [
            {
                name: 'class',
                value: IMPLIED_COL_SIZE_CLASS
            }
        ]
    };

//-------------------------------------------------===------------------------------------------------------
//                                          Template rendering
//-------------------------------------------------===------------------------------------------------------

function renderTemplateWithRegExpMatch(template, reGroupNames, reMatch) {
    if (reGroupNames.length > 0) {
        for (var i = 1; i < reMatch.length; i++)
            template = template.replace('{' + reGroupNames[i - 1] + '}', reMatch[i]);

    }

    return template;
}

//-------------------------------------------------===------------------------------------------------------
//                                           Tag conversion
//-------------------------------------------------===------------------------------------------------------

function getClassAttr(startTag) {
    var attrs = startTag.attrs;

    for (var i = 0; i < attrs.length; i++) {
        if (attrs[i].name === 'class')
            return attrs[i];
    }

    return null;
}

function convertToDiv(startTag, classNamesToAdd) {
    var classAttr = getClassAttr(startTag);

    if (typeof classNamesToAdd === 'string')
        classNamesToAdd = [classNamesToAdd];

    if (!classAttr) {
        classAttr = {name: 'class', value: ''};
        startTag.attrs.push(classAttr);
    }

    var classNames = classAttr.value.length ? classAttr.value.trim().split(/s+/) : [];

    classAttr.value = classNamesToAdd.concat(classNames).join(' ');
    startTag.tagName = 'div';

    return startTag;
}

//-------------------------------------------------===------------------------------------------------------
//                                        Attributes conversion
//-------------------------------------------------===------------------------------------------------------

//Converters
var ColumnAttrConverters = [
    {
        name: 'Size attribute',
        isCompound: false,
        classNameTemplate: 'col-{screenSize}-{size}',
        attr: {
            re: /^(xs|sm|md|lg)([1-9]|1[0-2])$/,
            reGroupNames: ['screenSize', 'size']
        }
    },

    {
        name: 'Compound size attribute',
        isCompound: true,
        classNameTemplate: 'col-{screenSize}-{size}',
        attr: {
            re: /^(size):(\S+)$/,
            reGroupNames: []
        },
        modifier: {
            re: /^(xs|sm|md|lg)([1-9]|1[0-2])$/,
            reGroupNames: ['screenSize', 'size']
        }
    },

    {
        name: 'Numbered property attribute (offsetN, pullN, pushN)',
        isCompound: false,
        classNameTemplate: 'col-md-{name}-{size}',
        attr: {
            re: /^(offset|push|pull)([0-9]|1[0-2])$/,
            reGroupNames: ['name', 'size']
        }
    },

    {
        name: 'Compound property attribute (offset:, pull:, push:)',
        isCompound: true,
        classNameTemplate: 'col-{screenSize}-{name}-{size}',
        attr: {
            re: /^(offset|push|pull):(\S+)$/,
            reGroupNames: ['name']
        },
        modifier: {
            re: /^(xs|sm|md|lg)([0-9]|1[0-2])$/,
            reGroupNames: ['screenSize', 'size']
        }
    }
];

//Converter methods builder
function buildAttrConverterMethods(converter) {
    converter.condition = function (attrName) {
        return converter.attr.re.test(attrName);
    };

    converter.toClassNames = function (attrName) {
        var attrMatch = attrName.match(converter.attr.re),
            classNames = void 0;

        if (converter.isCompound) {
            var modifiers = attrMatch[2];

            classNames = modifiers
                .trim()
                .split('-')
                .filter(function (item) {
                    return converter.modifier.re.test(item);
                })
                .map(function (modifier) {
                    var match = modifier.match(converter.modifier.re);

                    return renderTemplateWithRegExpMatch(
                        converter.classNameTemplate,
                        converter.modifier.reGroupNames,
                        match
                    );
                })
        }
        else
            classNames = [converter.classNameTemplate];

        return classNames.map(function (className) {
            return renderTemplateWithRegExpMatch(className, converter.attr.reGroupNames, attrMatch);
        });
    };
}

//Build converter methods
ColumnAttrConverters.forEach(buildAttrConverterMethods);

//Convert attributes to CSS class names
function convertAttrsToClassNames(attrs, converters) {
    var attrClassNames = [];

    for (var i = attrs.length - 1; i >= 0; i--) {
        for (var j = 0; j < converters.length; j++) {
            var attrName = attrs[i].name;

            if (converters[j].condition(attrName)) {
                var converted = converters[j].toClassNames(attrName);

                //NOTE: since we traverse in reverse order we concatenate
                //existing class names to the newly converted
                attrClassNames = converted.concat(attrClassNames);
                attrs.splice(i, 1);

                break;
            }
        }
    }

    return attrClassNames;
}

//-------------------------------------------------===------------------------------------------------------
//                                              Transpiler
//-------------------------------------------------===------------------------------------------------------

var GridTranspiler = function (ctx) {
    this.ctx = ctx;
    this.pendingRowTokens = [];
    this.expectingColStartTag = false;
    this.rowImpliedColFlagsStack = [];
};

//Clearfix processing
GridTranspiler.prototype._processClearfix = function (startTag) {
    //NOTE: if <clearfix> is self-closing then emit implied end tag
    if (startTag.selfClosing) {
        startTag.selfClosing = false;
        this.ctx.emit.endTag('div');
    }

    return convertToDiv(startTag, CLEARFIX_CLASS)
};

//Row processing
GridTranspiler.prototype._processRowStartTag = function (startTag) {
    this.expectingColStartTag = true;
    this.rowImpliedColFlagsStack.push(false);

    return convertToDiv(startTag, ROW_CLASS);
};

GridTranspiler.prototype._processRowEndTag = function () {
    var impliedColFlag = this.rowImpliedColFlagsStack.pop();

    if (impliedColFlag) {
        //NOTE: both column and row end tags transpiles to

        //so we can just emit additional
 here.
        this.ctx.emit.endTag('div');
    }

    return 'div';
};

GridTranspiler.prototype._ensureImpliedColStartTag = function () {
    if (this.expectingColStartTag) {
        //NOTE: mark topmost <r> on the stack with implied column flag
        var stackTop = this.rowImpliedColFlagsStack.length - 1;
        this.rowImpliedColFlagsStack[stackTop] = true;
        this.expectingColStartTag = false;

        //NOTE: emit implied column and insert pending row tokens after it
        this.ctx.emit.startTag(IMPLIED_COL_START_TAG_TOKEN);
        this._flushPendingRowTokens();

        return true;
    }

    return false;
};

GridTranspiler.prototype._flushPendingRowTokens = function () {
    if (this.pendingRowTokens.length) {
        for (var i = 0; i < this.pendingRowTokens.length; i++) {
            var tokenEntry = this.pendingRowTokens[i];
            this.ctx.emit[tokenEntry.type](tokenEntry.token);
        }

        this.pendingRowTokens = [];
        return true;
    }

    return false;
};

//Columns
GridTranspiler.prototype._isColTagName = function (tn) {
    return tn === COL_TAG || NUMBERED_COL_TAG_REGEXP.test(tn);
};

GridTranspiler.prototype._convertColTagToSizeClassName = function (tn) {
    if (tn === COL_TAG)
        return null;

    var match = tn.match(NUMBERED_COL_TAG_REGEXP);
    return renderTemplateWithRegExpMatch(NUMBERED_COL_CLASS_TEMPLATE, ['size'], match);
};

GridTranspiler.prototype._convertColAttrsToClassNames = function (attrs, sizeClassName) {
    var attrClassNames = convertAttrsToClassNames(attrs, ColumnAttrConverters);

    if (!sizeClassName) {
        var hasSizeAttrs = false;

        for (var i = 0; i < attrClassNames.length; i++) {
            if (SIZE_CLASS_REGEXP.test(attrClassNames[i])) {
                hasSizeAttrs = true;
                break;
            }
        }

        //NOTE: if it's not a numbered column and we don't have size class name
        //then fallback to the default column size
        if (!hasSizeAttrs)
            sizeClassName = DEFAULT_COL_SIZE_CLASS;
    }

    if (sizeClassName)
        attrClassNames.splice(0, 0, sizeClassName);

    return attrClassNames;
};

GridTranspiler.prototype._processColStartTag = function (startTag) {
    this.expectingColStartTag = false;

    //NOTE: if we have pending row tokens, insert them before column
    if (this._flushPendingRowTokens()) {
        this.ctx.emit.startTag(startTag);
        return null;
    }

    var sizeClassName = this._convertColTagToSizeClassName(startTag.tagName),
        classNames = this._convertColAttrsToClassNames(startTag.attrs, sizeClassName);

    return convertToDiv(startTag, classNames);
};

//Token handlers
GridTranspiler.prototype.onStartTag = function (startTag) {
    var tn = startTag.tagName,
        isCol = this._isColTagName(tn);

    if (isCol)
        return this._processColStartTag(startTag);

    if (this._ensureImpliedColStartTag()) {
        //NOTE: if we have additional tokens emitted we need to move current token after them
        this.ctx.emit.startTag(startTag);
        return null;
    }

    var containerClass = CONTAINER_TAG_CLASSES_MAP[tn];

    if (containerClass)
        return convertToDiv(startTag, containerClass);

    if (tn === CLEARFIX_TAG)
        return this._processClearfix(startTag);

    if (tn === ROW_TAG)
        return this._processRowStartTag(startTag);
};

GridTranspiler.prototype.onEndTag = function (tn) {
    if (this._ensureImpliedColStartTag()) {
        //NOTE: if we have additional tokens emitted we need to move current token after them
        this.ctx.emit.endTag(tn);
        return null;
    }

    if (tn === ROW_TAG)
        return this._processRowEndTag();

    if (CONTAINER_TAG_CLASSES_MAP[tn] || tn === CLEARFIX_TAG ||
        tn === COL_TAG || NUMBERED_COL_TAG_REGEXP.test(tn)) {
        //NOTE: end tag is always transpiled to

        return 'div';
    }
};

GridTranspiler.prototype.onText = function (text) {
    //NOTE: new lines and spaces do not trigger implied column generation.
    //Therefore we don't know how it will be adopted, let's store it for now.
    if (this.expectingColStartTag && !text.trim()) {
        this.pendingRowTokens.push({type: 'text', token: text});
        return null;
    }

    if (this._ensureImpliedColStartTag()) {
        //NOTE: if we have additional tokens emitted we need to move current token after them
        this.ctx.emit.text(text);
        return null;
    }
};

GridTranspiler.prototype.onComment = function (comment) {
    //NOTE: like with whitespace text in row we need to decide
    //later who will adopt comment. So we just store it for now.
    if (this.expectingColStartTag) {
        this.pendingRowTokens.push({type: 'comment', token: comment});
        return null;
    }
};

//-------------------------------------------------===------------------------------------------------------
//                                             ineed plugin
//-------------------------------------------------===------------------------------------------------------

module.exports = {
    name: 'grid',
    extends: 'reprocess',

    init: function (ctx) {
        var plugin = this,
            transpiler = new GridTranspiler(ctx);

        ['onStartTag', 'onEndTag', 'onText', 'onComment'].forEach(function (handler) {
            plugin[handler] = function (token) {
                return transpiler[handler](token);
            };
        });
    }
};
<commit_msg>Grid: isCompound->compoundAttr<commit_after>//-------------------------------------------------===------------------------------------------------------
//                                                Const
//-------------------------------------------------===------------------------------------------------------

//Containers
var CONTAINER_TAG_CLASSES_MAP = {
    'container': 'container',
    'container-fluid': 'container-fluid'
};

//Clearfix
var CLEARFIX_TAG =
    CLEARFIX_CLASS = 'clearfix';

//Columns
var COL_TAG = 'c',
    SIZE_CLASS_REGEXP = /^col-(xs|sm|md|lg)-([1-9]|1[0-2])$/,
    NUMBERED_COL_TAG_REGEXP = /^c([1-9]|1[0-2])$/,
    NUMBERED_COL_CLASS_TEMPLATE = 'col-md-{size}',
    DEFAULT_COL_SIZE_CLASS = 'col-md-1',
    IMPLIED_COL_SIZE_CLASS = 'col-md-12';

//Row
var ROW_TAG = 'r',
    ROW_CLASS = 'row',
    IMPLIED_COL_START_TAG_TOKEN = {
        tagName: 'div',
        selfClosing: false,
        attrs: [
            {
                name: 'class',
                value: IMPLIED_COL_SIZE_CLASS
            }
        ]
    };

//-------------------------------------------------===------------------------------------------------------
//                                          Template rendering
//-------------------------------------------------===------------------------------------------------------

function renderTemplateWithRegExpMatch(template, reGroupNames, reMatch) {
    if (reGroupNames.length > 0) {
        for (var i = 1; i < reMatch.length; i++)
            template = template.replace('{' + reGroupNames[i - 1] + '}', reMatch[i]);

    }

    return template;
}

//-------------------------------------------------===------------------------------------------------------
//                                           Tag conversion
//-------------------------------------------------===------------------------------------------------------

function getClassAttr(startTag) {
    var attrs = startTag.attrs;

    for (var i = 0; i < attrs.length; i++) {
        if (attrs[i].name === 'class')
            return attrs[i];
    }

    return null;
}

function convertToDiv(startTag, classNamesToAdd) {
    var classAttr = getClassAttr(startTag);

    if (typeof classNamesToAdd === 'string')
        classNamesToAdd = [classNamesToAdd];

    if (!classAttr) {
        classAttr = {name: 'class', value: ''};
        startTag.attrs.push(classAttr);
    }

    var classNames = classAttr.value.length ? classAttr.value.trim().split(/s+/) : [];

    classAttr.value = classNamesToAdd.concat(classNames).join(' ');
    startTag.tagName = 'div';

    return startTag;
}

//-------------------------------------------------===------------------------------------------------------
//                                        Attributes conversion
//-------------------------------------------------===------------------------------------------------------

//Converters
var ColumnAttrConverters = [
    {
        name: 'Size attribute',
        compoundAttr: false,
        classNameTemplate: 'col-{screenSize}-{size}',
        attr: {
            re: /^(xs|sm|md|lg)([1-9]|1[0-2])$/,
            reGroupNames: ['screenSize', 'size']
        }
    },

    {
        name: 'Compound size attribute',
        compoundAttr: true,
        classNameTemplate: 'col-{screenSize}-{size}',
        attr: {
            re: /^(size):(\S+)$/,
            reGroupNames: []
        },
        modifier: {
            re: /^(xs|sm|md|lg)([1-9]|1[0-2])$/,
            reGroupNames: ['screenSize', 'size']
        }
    },

    {
        name: 'Numbered property attribute (offsetN, pullN, pushN)',
        compoundAttr: false,
        classNameTemplate: 'col-md-{name}-{size}',
        attr: {
            re: /^(offset|push|pull)([0-9]|1[0-2])$/,
            reGroupNames: ['name', 'size']
        }
    },

    {
        name: 'Compound property attribute (offset:, pull:, push:)',
        compoundAttr: true,
        classNameTemplate: 'col-{screenSize}-{name}-{size}',
        attr: {
            re: /^(offset|push|pull):(\S+)$/,
            reGroupNames: ['name']
        },
        modifier: {
            re: /^(xs|sm|md|lg)([0-9]|1[0-2])$/,
            reGroupNames: ['screenSize', 'size']
        }
    }
];

//Converter methods builder
function buildAttrConverterMethods(converter) {
    converter.condition = function (attrName) {
        return converter.attr.re.test(attrName);
    };

    converter.toClassNames = function (attrName) {
        var attrMatch = attrName.match(converter.attr.re),
            classNames = void 0;

        if (converter.compoundAttr) {
            var modifiers = attrMatch[2];

            classNames = modifiers
                .trim()
                .split('-')
                .filter(function (item) {
                    return converter.modifier.re.test(item);
                })
                .map(function (modifier) {
                    var match = modifier.match(converter.modifier.re);

                    return renderTemplateWithRegExpMatch(
                        converter.classNameTemplate,
                        converter.modifier.reGroupNames,
                        match
                    );
                })
        }
        else
            classNames = [converter.classNameTemplate];

        return classNames.map(function (className) {
            return renderTemplateWithRegExpMatch(className, converter.attr.reGroupNames, attrMatch);
        });
    };
}

//Build converter methods
ColumnAttrConverters.forEach(buildAttrConverterMethods);

//Convert attributes to CSS class names
function convertAttrsToClassNames(attrs, converters) {
    var attrClassNames = [];

    for (var i = attrs.length - 1; i >= 0; i--) {
        for (var j = 0; j < converters.length; j++) {
            var attrName = attrs[i].name;

            if (converters[j].condition(attrName)) {
                var converted = converters[j].toClassNames(attrName);

                //NOTE: since we traverse in reverse order we concatenate
                //existing class names to the newly converted
                attrClassNames = converted.concat(attrClassNames);
                attrs.splice(i, 1);

                break;
            }
        }
    }

    return attrClassNames;
}

//-------------------------------------------------===------------------------------------------------------
//                                              Transpiler
//-------------------------------------------------===------------------------------------------------------

var GridTranspiler = function (ctx) {
    this.ctx = ctx;
    this.pendingRowTokens = [];
    this.expectingColStartTag = false;
    this.rowImpliedColFlagsStack = [];
};

//Clearfix processing
GridTranspiler.prototype._processClearfix = function (startTag) {
    //NOTE: if <clearfix> is self-closing then emit implied end tag
    if (startTag.selfClosing) {
        startTag.selfClosing = false;
        this.ctx.emit.endTag('div');
    }

    return convertToDiv(startTag, CLEARFIX_CLASS)
};

//Row processing
GridTranspiler.prototype._processRowStartTag = function (startTag) {
    this.expectingColStartTag = true;
    this.rowImpliedColFlagsStack.push(false);

    return convertToDiv(startTag, ROW_CLASS);
};

GridTranspiler.prototype._processRowEndTag = function () {
    var impliedColFlag = this.rowImpliedColFlagsStack.pop();

    if (impliedColFlag) {
        //NOTE: both column and row end tags transpiles to

        //so we can just emit additional
 here.
        this.ctx.emit.endTag('div');
    }

    return 'div';
};

GridTranspiler.prototype._ensureImpliedColStartTag = function () {
    if (this.expectingColStartTag) {
        //NOTE: mark topmost <r> on the stack with implied column flag
        var stackTop = this.rowImpliedColFlagsStack.length - 1;
        this.rowImpliedColFlagsStack[stackTop] = true;
        this.expectingColStartTag = false;

        //NOTE: emit implied column and insert pending row tokens after it
        this.ctx.emit.startTag(IMPLIED_COL_START_TAG_TOKEN);
        this._flushPendingRowTokens();

        return true;
    }

    return false;
};

GridTranspiler.prototype._flushPendingRowTokens = function () {
    if (this.pendingRowTokens.length) {
        for (var i = 0; i < this.pendingRowTokens.length; i++) {
            var tokenEntry = this.pendingRowTokens[i];
            this.ctx.emit[tokenEntry.type](tokenEntry.token);
        }

        this.pendingRowTokens = [];
        return true;
    }

    return false;
};

//Columns
GridTranspiler.prototype._isColTagName = function (tn) {
    return tn === COL_TAG || NUMBERED_COL_TAG_REGEXP.test(tn);
};

GridTranspiler.prototype._convertColTagToSizeClassName = function (tn) {
    if (tn === COL_TAG)
        return null;

    var match = tn.match(NUMBERED_COL_TAG_REGEXP);
    return renderTemplateWithRegExpMatch(NUMBERED_COL_CLASS_TEMPLATE, ['size'], match);
};

GridTranspiler.prototype._convertColAttrsToClassNames = function (attrs, sizeClassName) {
    var attrClassNames = convertAttrsToClassNames(attrs, ColumnAttrConverters);

    if (!sizeClassName) {
        var hasSizeAttrs = false;

        for (var i = 0; i < attrClassNames.length; i++) {
            if (SIZE_CLASS_REGEXP.test(attrClassNames[i])) {
                hasSizeAttrs = true;
                break;
            }
        }

        //NOTE: if it's not a numbered column and we don't have size class name
        //then fallback to the default column size
        if (!hasSizeAttrs)
            sizeClassName = DEFAULT_COL_SIZE_CLASS;
    }

    if (sizeClassName)
        attrClassNames.splice(0, 0, sizeClassName);

    return attrClassNames;
};

GridTranspiler.prototype._processColStartTag = function (startTag) {
    this.expectingColStartTag = false;

    //NOTE: if we have pending row tokens, insert them before column
    if (this._flushPendingRowTokens()) {
        this.ctx.emit.startTag(startTag);
        return null;
    }

    var sizeClassName = this._convertColTagToSizeClassName(startTag.tagName),
        classNames = this._convertColAttrsToClassNames(startTag.attrs, sizeClassName);

    return convertToDiv(startTag, classNames);
};

//Token handlers
GridTranspiler.prototype.onStartTag = function (startTag) {
    var tn = startTag.tagName,
        isCol = this._isColTagName(tn);

    if (isCol)
        return this._processColStartTag(startTag);

    if (this._ensureImpliedColStartTag()) {
        //NOTE: if we have additional tokens emitted we need to move current token after them
        this.ctx.emit.startTag(startTag);
        return null;
    }

    var containerClass = CONTAINER_TAG_CLASSES_MAP[tn];

    if (containerClass)
        return convertToDiv(startTag, containerClass);

    if (tn === CLEARFIX_TAG)
        return this._processClearfix(startTag);

    if (tn === ROW_TAG)
        return this._processRowStartTag(startTag);
};

GridTranspiler.prototype.onEndTag = function (tn) {
    if (this._ensureImpliedColStartTag()) {
        //NOTE: if we have additional tokens emitted we need to move current token after them
        this.ctx.emit.endTag(tn);
        return null;
    }

    if (tn === ROW_TAG)
        return this._processRowEndTag();

    if (CONTAINER_TAG_CLASSES_MAP[tn] || tn === CLEARFIX_TAG ||
        tn === COL_TAG || NUMBERED_COL_TAG_REGEXP.test(tn)) {
        //NOTE: end tag is always transpiled to

        return 'div';
    }
};

GridTranspiler.prototype.onText = function (text) {
    //NOTE: new lines and spaces do not trigger implied column generation.
    //Therefore we don't know how it will be adopted, let's store it for now.
    if (this.expectingColStartTag && !text.trim()) {
        this.pendingRowTokens.push({type: 'text', token: text});
        return null;
    }

    if (this._ensureImpliedColStartTag()) {
        //NOTE: if we have additional tokens emitted we need to move current token after them
        this.ctx.emit.text(text);
        return null;
    }
};

GridTranspiler.prototype.onComment = function (comment) {
    //NOTE: like with whitespace text in row we need to decide
    //later who will adopt comment. So we just store it for now.
    if (this.expectingColStartTag) {
        this.pendingRowTokens.push({type: 'comment', token: comment});
        return null;
    }
};

//-------------------------------------------------===------------------------------------------------------
//                                             ineed plugin
//-------------------------------------------------===------------------------------------------------------

module.exports = {
    name: 'grid',
    extends: 'reprocess',

    init: function (ctx) {
        var plugin = this,
            transpiler = new GridTranspiler(ctx);

        ['onStartTag', 'onEndTag', 'onText', 'onComment'].forEach(function (handler) {
            plugin[handler] = function (token) {
                return transpiler[handler](token);
            };
        });
    }
};
<|endoftext|>