284 lines
36 KiB
JavaScript
284 lines
36 KiB
JavaScript
|
'use strict';
|
|||
|
|
|||
|
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
|
|||
|
|
|||
|
var _resolve = require('eslint-module-utils/resolve');
|
|||
|
|
|||
|
var _resolve2 = _interopRequireDefault(_resolve);
|
|||
|
|
|||
|
var _docsUrl = require('../docsUrl');
|
|||
|
|
|||
|
var _docsUrl2 = _interopRequireDefault(_docsUrl);
|
|||
|
|
|||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|||
|
|
|||
|
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } else { return Array.from(arr); } }
|
|||
|
|
|||
|
function _toArray(arr) { return Array.isArray(arr) ? arr : Array.from(arr); }
|
|||
|
|
|||
|
function checkImports(imported, context) {
|
|||
|
for (const _ref of imported.entries()) {
|
|||
|
var _ref2 = _slicedToArray(_ref, 2);
|
|||
|
|
|||
|
const module = _ref2[0];
|
|||
|
const nodes = _ref2[1];
|
|||
|
|
|||
|
if (nodes.length > 1) {
|
|||
|
const message = `'${module}' imported multiple times.`;
|
|||
|
|
|||
|
var _nodes = _toArray(nodes);
|
|||
|
|
|||
|
const first = _nodes[0],
|
|||
|
rest = _nodes.slice(1);
|
|||
|
|
|||
|
const sourceCode = context.getSourceCode();
|
|||
|
const fix = getFix(first, rest, sourceCode);
|
|||
|
|
|||
|
context.report({
|
|||
|
node: first.source,
|
|||
|
message,
|
|||
|
fix // Attach the autofix (if any) to the first import.
|
|||
|
});
|
|||
|
|
|||
|
for (const node of rest) {
|
|||
|
context.report({
|
|||
|
node: node.source,
|
|||
|
message
|
|||
|
});
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function getFix(first, rest, sourceCode) {
|
|||
|
// Sorry ESLint <= 3 users, no autofix for you. Autofixing duplicate imports
|
|||
|
// requires multiple `fixer.whatever()` calls in the `fix`: We both need to
|
|||
|
// update the first one, and remove the rest. Support for multiple
|
|||
|
// `fixer.whatever()` in a single `fix` was added in ESLint 4.1.
|
|||
|
// `sourceCode.getCommentsBefore` was added in 4.0, so that's an easy thing to
|
|||
|
// check for.
|
|||
|
if (typeof sourceCode.getCommentsBefore !== 'function') {
|
|||
|
return undefined;
|
|||
|
}
|
|||
|
|
|||
|
// Adjusting the first import might make it multiline, which could break
|
|||
|
// `eslint-disable-next-line` comments and similar, so bail if the first
|
|||
|
// import has comments. Also, if the first import is `import * as ns from
|
|||
|
// './foo'` there's nothing we can do.
|
|||
|
if (hasProblematicComments(first, sourceCode) || hasNamespace(first)) {
|
|||
|
return undefined;
|
|||
|
}
|
|||
|
|
|||
|
const defaultImportNames = new Set([first].concat(_toConsumableArray(rest)).map(getDefaultImportName).filter(Boolean));
|
|||
|
|
|||
|
// Bail if there are multiple different default import names – it's up to the
|
|||
|
// user to choose which one to keep.
|
|||
|
if (defaultImportNames.size > 1) {
|
|||
|
return undefined;
|
|||
|
}
|
|||
|
|
|||
|
// Leave it to the user to handle comments. Also skip `import * as ns from
|
|||
|
// './foo'` imports, since they cannot be merged into another import.
|
|||
|
const restWithoutComments = rest.filter(node => !(hasProblematicComments(node, sourceCode) || hasNamespace(node)));
|
|||
|
|
|||
|
const specifiers = restWithoutComments.map(node => {
|
|||
|
const tokens = sourceCode.getTokens(node);
|
|||
|
const openBrace = tokens.find(token => isPunctuator(token, '{'));
|
|||
|
const closeBrace = tokens.find(token => isPunctuator(token, '}'));
|
|||
|
|
|||
|
if (openBrace == null || closeBrace == null) {
|
|||
|
return undefined;
|
|||
|
}
|
|||
|
|
|||
|
return {
|
|||
|
importNode: node,
|
|||
|
text: sourceCode.text.slice(openBrace.range[1], closeBrace.range[0]),
|
|||
|
hasTrailingComma: isPunctuator(sourceCode.getTokenBefore(closeBrace), ','),
|
|||
|
isEmpty: !hasSpecifiers(node)
|
|||
|
};
|
|||
|
}).filter(Boolean);
|
|||
|
|
|||
|
const unnecessaryImports = restWithoutComments.filter(node => !hasSpecifiers(node) && !hasNamespace(node) && !specifiers.some(specifier => specifier.importNode === node));
|
|||
|
|
|||
|
const shouldAddDefault = getDefaultImportName(first) == null && defaultImportNames.size === 1;
|
|||
|
const shouldAddSpecifiers = specifiers.length > 0;
|
|||
|
const shouldRemoveUnnecessary = unnecessaryImports.length > 0;
|
|||
|
|
|||
|
if (!(shouldAddDefault || shouldAddSpecifiers || shouldRemoveUnnecessary)) {
|
|||
|
return undefined;
|
|||
|
}
|
|||
|
|
|||
|
return fixer => {
|
|||
|
const tokens = sourceCode.getTokens(first);
|
|||
|
const openBrace = tokens.find(token => isPunctuator(token, '{'));
|
|||
|
const closeBrace = tokens.find(token => isPunctuator(token, '}'));
|
|||
|
const firstToken = sourceCode.getFirstToken(first);
|
|||
|
|
|||
|
var _defaultImportNames = _slicedToArray(defaultImportNames, 1);
|
|||
|
|
|||
|
const defaultImportName = _defaultImportNames[0];
|
|||
|
|
|||
|
|
|||
|
const firstHasTrailingComma = closeBrace != null && isPunctuator(sourceCode.getTokenBefore(closeBrace), ',');
|
|||
|
const firstIsEmpty = !hasSpecifiers(first);
|
|||
|
|
|||
|
var _specifiers$reduce = specifiers.reduce((_ref3, specifier) => {
|
|||
|
var _ref4 = _slicedToArray(_ref3, 2);
|
|||
|
|
|||
|
let result = _ref4[0],
|
|||
|
needsComma = _ref4[1];
|
|||
|
|
|||
|
return [needsComma && !specifier.isEmpty ? `${result},${specifier.text}` : `${result}${specifier.text}`, specifier.isEmpty ? needsComma : true];
|
|||
|
}, ['', !firstHasTrailingComma && !firstIsEmpty]),
|
|||
|
_specifiers$reduce2 = _slicedToArray(_specifiers$reduce, 1);
|
|||
|
|
|||
|
const specifiersText = _specifiers$reduce2[0];
|
|||
|
|
|||
|
|
|||
|
const fixes = [];
|
|||
|
|
|||
|
if (shouldAddDefault && openBrace == null && shouldAddSpecifiers) {
|
|||
|
// `import './foo'` → `import def, {...} from './foo'`
|
|||
|
fixes.push(fixer.insertTextAfter(firstToken, ` ${defaultImportName}, {${specifiersText}} from`));
|
|||
|
} else if (shouldAddDefault && openBrace == null && !shouldAddSpecifiers) {
|
|||
|
// `import './foo'` → `import def from './foo'`
|
|||
|
fixes.push(fixer.insertTextAfter(firstToken, ` ${defaultImportName} from`));
|
|||
|
} else if (shouldAddDefault && openBrace != null && closeBrace != null) {
|
|||
|
// `import {...} from './foo'` → `import def, {...} from './foo'`
|
|||
|
fixes.push(fixer.insertTextAfter(firstToken, ` ${defaultImportName},`));
|
|||
|
if (shouldAddSpecifiers) {
|
|||
|
// `import def, {...} from './foo'` → `import def, {..., ...} from './foo'`
|
|||
|
fixes.push(fixer.insertTextBefore(closeBrace, specifiersText));
|
|||
|
}
|
|||
|
} else if (!shouldAddDefault && openBrace == null && shouldAddSpecifiers) {
|
|||
|
// `import './foo'` → `import {...} from './foo'`
|
|||
|
fixes.push(fixer.insertTextAfter(firstToken, ` {${specifiersText}} from`));
|
|||
|
} else if (!shouldAddDefault && openBrace != null && closeBrace != null) {
|
|||
|
// `import {...} './foo'` → `import {..., ...} from './foo'`
|
|||
|
fixes.push(fixer.insertTextBefore(closeBrace, specifiersText));
|
|||
|
}
|
|||
|
|
|||
|
// Remove imports whose specifiers have been moved into the first import.
|
|||
|
for (const specifier of specifiers) {
|
|||
|
fixes.push(fixer.remove(specifier.importNode));
|
|||
|
}
|
|||
|
|
|||
|
// Remove imports whose default import has been moved to the first import,
|
|||
|
// and side-effect-only imports that are unnecessary due to the first
|
|||
|
// import.
|
|||
|
for (const node of unnecessaryImports) {
|
|||
|
fixes.push(fixer.remove(node));
|
|||
|
}
|
|||
|
|
|||
|
return fixes;
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
function isPunctuator(node, value) {
|
|||
|
return node.type === 'Punctuator' && node.value === value;
|
|||
|
}
|
|||
|
|
|||
|
// Get the name of the default import of `node`, if any.
|
|||
|
function getDefaultImportName(node) {
|
|||
|
const defaultSpecifier = node.specifiers.find(specifier => specifier.type === 'ImportDefaultSpecifier');
|
|||
|
return defaultSpecifier != null ? defaultSpecifier.local.name : undefined;
|
|||
|
}
|
|||
|
|
|||
|
// Checks whether `node` has a namespace import.
|
|||
|
function hasNamespace(node) {
|
|||
|
const specifiers = node.specifiers.filter(specifier => specifier.type === 'ImportNamespaceSpecifier');
|
|||
|
return specifiers.length > 0;
|
|||
|
}
|
|||
|
|
|||
|
// Checks whether `node` has any non-default specifiers.
|
|||
|
function hasSpecifiers(node) {
|
|||
|
const specifiers = node.specifiers.filter(specifier => specifier.type === 'ImportSpecifier');
|
|||
|
return specifiers.length > 0;
|
|||
|
}
|
|||
|
|
|||
|
// It's not obvious what the user wants to do with comments associated with
|
|||
|
// duplicate imports, so skip imports with comments when autofixing.
|
|||
|
function hasProblematicComments(node, sourceCode) {
|
|||
|
return hasCommentBefore(node, sourceCode) || hasCommentAfter(node, sourceCode) || hasCommentInsideNonSpecifiers(node, sourceCode);
|
|||
|
}
|
|||
|
|
|||
|
// Checks whether `node` has a comment (that ends) on the previous line or on
|
|||
|
// the same line as `node` (starts).
|
|||
|
function hasCommentBefore(node, sourceCode) {
|
|||
|
return sourceCode.getCommentsBefore(node).some(comment => comment.loc.end.line >= node.loc.start.line - 1);
|
|||
|
}
|
|||
|
|
|||
|
// Checks whether `node` has a comment (that starts) on the same line as `node`
|
|||
|
// (ends).
|
|||
|
function hasCommentAfter(node, sourceCode) {
|
|||
|
return sourceCode.getCommentsAfter(node).some(comment => comment.loc.start.line === node.loc.end.line);
|
|||
|
}
|
|||
|
|
|||
|
// Checks whether `node` has any comments _inside,_ except inside the `{...}`
|
|||
|
// part (if any).
|
|||
|
function hasCommentInsideNonSpecifiers(node, sourceCode) {
|
|||
|
const tokens = sourceCode.getTokens(node);
|
|||
|
const openBraceIndex = tokens.findIndex(token => isPunctuator(token, '{'));
|
|||
|
const closeBraceIndex = tokens.findIndex(token => isPunctuator(token, '}'));
|
|||
|
// Slice away the first token, since we're no looking for comments _before_
|
|||
|
// `node` (only inside). If there's a `{...}` part, look for comments before
|
|||
|
// the `{`, but not before the `}` (hence the `+1`s).
|
|||
|
const someTokens = openBraceIndex >= 0 && closeBraceIndex >= 0 ? tokens.slice(1, openBraceIndex + 1).concat(tokens.slice(closeBraceIndex + 1)) : tokens.slice(1);
|
|||
|
return someTokens.some(token => sourceCode.getCommentsBefore(token).length > 0);
|
|||
|
}
|
|||
|
|
|||
|
module.exports = {
|
|||
|
meta: {
|
|||
|
type: 'problem',
|
|||
|
docs: {
|
|||
|
url: (0, _docsUrl2.default)('no-duplicates')
|
|||
|
},
|
|||
|
fixable: 'code',
|
|||
|
schema: [{
|
|||
|
type: 'object',
|
|||
|
properties: {
|
|||
|
considerQueryString: {
|
|||
|
type: 'boolean'
|
|||
|
}
|
|||
|
},
|
|||
|
additionalProperties: false
|
|||
|
}]
|
|||
|
},
|
|||
|
|
|||
|
create: function (context) {
|
|||
|
// Prepare the resolver from options.
|
|||
|
const considerQueryStringOption = context.options[0] && context.options[0]['considerQueryString'];
|
|||
|
const defaultResolver = sourcePath => (0, _resolve2.default)(sourcePath, context) || sourcePath;
|
|||
|
const resolver = considerQueryStringOption ? sourcePath => {
|
|||
|
const parts = sourcePath.match(/^([^?]*)\?(.*)$/);
|
|||
|
if (!parts) {
|
|||
|
return defaultResolver(sourcePath);
|
|||
|
}
|
|||
|
return defaultResolver(parts[1]) + '?' + parts[2];
|
|||
|
} : defaultResolver;
|
|||
|
|
|||
|
const imported = new Map();
|
|||
|
const nsImported = new Map();
|
|||
|
const typesImported = new Map();
|
|||
|
return {
|
|||
|
'ImportDeclaration': function (n) {
|
|||
|
// resolved path will cover aliased duplicates
|
|||
|
const resolvedPath = resolver(n.source.value);
|
|||
|
const importMap = n.importKind === 'type' ? typesImported : hasNamespace(n) ? nsImported : imported;
|
|||
|
|
|||
|
if (importMap.has(resolvedPath)) {
|
|||
|
importMap.get(resolvedPath).push(n);
|
|||
|
} else {
|
|||
|
importMap.set(resolvedPath, [n]);
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
'Program:exit': function () {
|
|||
|
checkImports(imported, context);
|
|||
|
checkImports(nsImported, context);
|
|||
|
checkImports(typesImported, context);
|
|||
|
}
|
|||
|
};
|
|||
|
}
|
|||
|
};
|
|||
|
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9ydWxlcy9uby1kdXBsaWNhdGVzLmpzIl0sIm5hbWVzIjpbImNoZWNrSW1wb3J0cyIsImltcG9ydGVkIiwiY29udGV4dCIsImVudHJpZXMiLCJtb2R1bGUiLCJub2RlcyIsImxlbmd0aCIsIm1lc3NhZ2UiLCJmaXJzdCIsInJlc3QiLCJzb3VyY2VDb2RlIiwiZ2V0U291cmNlQ29kZSIsImZpeCIsImdldEZpeCIsInJlcG9ydCIsIm5vZGUiLCJzb3VyY2UiLCJnZXRDb21tZW50c0JlZm9yZSIsInVuZGVmaW5lZCIsImhhc1Byb2JsZW1hdGljQ29tbWVudHMiLCJoYXNOYW1lc3BhY2UiLCJkZWZhdWx0SW1wb3J0TmFtZXMiLCJTZXQiLCJtYXAiLCJnZXREZWZhdWx0SW1wb3J0TmFtZSIsImZpbHRlciIsIkJvb2xlYW4iLCJzaXplIiwicmVzdFdpdGhvdXRDb21tZW50cyIsInNwZWNpZmllcnMiLCJ0b2tlbnMiLCJnZXRUb2tlbnMiLCJvcGVuQnJhY2UiLCJmaW5kIiwidG9rZW4iLCJpc1B1bmN0dWF0b3IiLCJjbG9zZUJyYWNlIiwiaW1wb3J0Tm9kZSIsInRleHQiLCJzbGljZSIsInJhbmdlIiwiaGFzVHJhaWxpbmdDb21tYSIsImdldFRva2VuQmVmb3JlIiwiaXNFbXB0eSIsImhhc1NwZWNpZmllcnMiLCJ1bm5lY2Vzc2FyeUltcG9ydHMiLCJzb21lIiwic3BlY2lmaWVyIiwic2hvdWxkQWRkRGVmYXVsdCIsInNob3VsZEFkZFNwZWNpZmllcnMiLCJzaG91bGRSZW1vdmVVbm5lY2Vzc2FyeSIsImZpeGVyIiwiZmlyc3RUb2tlbiIsImdldEZpcnN0VG9rZW4iLCJkZWZhdWx0SW1wb3J0TmFtZSIsImZpcnN0SGFzVHJhaWxpbmdDb21tYSIsImZpcnN0SXNFbXB0eSIsInJlZHVjZSIsInJlc3VsdCIsIm5lZWRzQ29tbWEiLCJzcGVjaWZpZXJzVGV4dCIsImZpeGVzIiwicHVzaCIsImluc2VydFRleHRBZnRlciIsImluc2VydFRleHRCZWZvcmUiLCJyZW1vdmUiLCJ2YWx1ZSIsInR5cGUiLCJkZWZhdWx0U3BlY2lmaWVyIiwibG9jYWwiLCJuYW1lIiwiaGFzQ29tbWVudEJlZm9yZSIsImhhc0NvbW1lbnRBZnRlciIsImhhc0NvbW1lbnRJbnNpZGVOb25TcGVjaWZpZXJzIiwiY29tbWVudCIsImxvYyIsImVuZCIsImxpbmUiLCJzdGFydCIsImdldENvbW1lbnRzQWZ0ZXIiLCJvcGVuQnJhY2VJbmRleCIsImZpbmRJbmRleCIsImNsb3NlQnJhY2VJbmRleCIsInNvbWVUb2tlbnMiLCJjb25jYXQiLCJleHBvcnRzIiwibWV0YSIsImRvY3MiLCJ1cmwiLCJmaXhhYmxlIiwic2NoZW1hIiwicHJvcGVydGllcyIsImNvbnNpZGVyUXVlcnlTdHJpbmciLCJhZGRpdGlvbmFsUHJvcGVydGllcyIsImNyZWF0ZSIsImNvbnNpZGVyUXVlcnlTdHJpbmdPcHRpb24iLCJvcHRpb25zIiwiZGVmYXVsdFJlc29sdmVyIiwic291cmNlUGF0aCIsInJlc29sdmVyIiwicGFydHMiLCJtYXRjaCIsIk1hcCIsIm5zSW1wb3J0ZWQiLCJ0eXBlc0ltcG9ydGVkIiwibiIsInJlc29sdmVkUGF0aCIsImltcG9ydE1hcCIsImltcG9ydEtpbmQiLCJoYXMiLCJnZXQiLCJzZXQiXSwibWFwcGluZ3MiOiI7Ozs7QUFBQTs7OztBQUNBOzs7Ozs7Ozs7O0FBRUEsU0FBU0EsWUFBVCxDQUFzQkMsUUFBdEIsRUFBZ0NDLE9BQWhDLEVBQXlDO0FBQ3ZDLHFCQUE4QkQsU0FBU0UsT0FBVCxFQUE5QixFQUFrRDtBQUFBOztBQUFBLFVBQXRDQyxNQUFzQztBQUFBLFVBQTlCQyxLQUE4Qjs7QUFDaEQsUUFBSUEsTUFBTUMsTUFBTixHQUFlLENBQW5CLEVBQXNCO0FBQ3BCLFlBQU1DLFVBQVcsSUFBR0gsTUFBTyw0QkFBM0I7O0FBRG9CLDRCQUVLQyxLQUZMOztBQUFBLFlBRWJHLEtBRmE7QUFBQSxZQUVIQyxJQUZHOztBQUdwQixZQUFNQyxhQUFhUixRQUFRUyxhQUFSLEVBQW5CO0FBQ0EsWUFBTUMsTUFBTUMsT0FBT0wsS0FBUCxFQUFjQyxJQUFkLEVBQW9CQyxVQUFwQixDQUFaOztBQUVBUixjQUFRWSxNQUFSLENBQWU7QUFDYkMsY0FBTVAsTUFBTVEsTUFEQztBQUViVCxlQUZhO0FBR2JLLFdBSGEsQ0FHUjtBQUhRLE9BQWY7O0FBTUEsV0FBSyxNQUFNRyxJQUFYLElBQW1CTixJQUFuQixFQUF5QjtBQUN2QlAsZ0JBQVFZLE1BQVIsQ0FBZTtBQUNiQyxnQkFBTUEsS0FBS0MsTUFERTtBQUViVDtBQUZhLFNBQWY7QUFJRDtBQUNGO0FBQ0Y7QUFDRjs7QUFFRCxTQUFTTSxNQUFULENBQWdCTCxLQUFoQixFQUF1QkMsSUFBdkIsRUFBNkJDLFVBQTdCLEVBQXlDO0FBQ3ZDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQUksT0FBT0EsV0FBV08saUJBQWxCLEtBQXdDLFVBQTVDLEVBQXdEO0FBQ3RELFdBQU9DLFNBQVA7QUFDRDs7QUFFRDtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQUlDLHVCQUF1QlgsS0FBdkIsRUFBOEJFLFVBQTlCLEtBQTZDVSxhQUFhWixLQUFiLENBQWpELEVBQXNFO0FBQ3BFLFdBQU9VLFNBQVA7QUFDRDs7QUFFRCxRQUFNRyxxQkFBcUIsSUFBSUMsR0FBSixDQUN6QixDQUFDZCxLQUFELDRCQUFXQyxJQUFYLEdBQWlCYyxHQUFqQixDQUFxQkMsb0JBQXJCLEVBQTJDQyxNQUEzQyxDQUFrREMsT0FBbEQsQ0FEeUIsQ0FBM0I7O0FBSUE7QUFDQTtBQUNBLE1BQUlMLG1CQUFtQk0sSUFBbkIsR0FBMEIsQ0FBOUIsRUFBaUM7QUFDL0IsV0FBT1QsU0FBUDtBQUNEOztBQUVEO0FBQ0E7QUFDQSxRQUFNVSxzQkFBc0JuQixLQUFLZ0IsTUFBTCxDQUFZVixRQUFRLEVBQzlDSSx1QkFBdUJKLElBQXZCLEVBQTZCTCxVQUE3QixLQUNBVSxhQUFhTCxJQUFiLENBRjhDLENBQXBCLENBQTVCOztBQUtBLFFBQU1jLGFBQWFELG9CQUNoQkwsR0FEZ0IsQ0FDWlIsUUFBUTtBQUNYLFVBQU1lLFNBQVNwQixXQUFXcUIsU0FBWCxDQUFxQmhCLElBQXJCLENBQWY7QUFDQSxVQUFNaUIsWUFBWUYsT0FBT0csSUFBUCxDQUFZQyxTQUFTQyxhQUFhRCxLQUFiLEVBQW9CLEdBQXBCLENBQXJCLENBQWxCO0FBQ0EsVUFBTUUsYUFBYU4sT0FBT0csSUFBUCxDQUFZQyxTQUFTQyxhQUFhRCxLQUFiLEVBQW9CLEdBQXBCLENBQXJCLENBQW5COztBQUVBLFFBQUlGLGFBQWEsSUFBYixJQUFxQkksY0FBYyxJQUF2QyxFQUE2QztBQUMzQyxhQUFPbEIsU0FBUDtBQUNEOztBQUVELFdBQU87QUFDTG1CLGtCQUFZdEIsSUFEUDtBQUVMdUIsWUFBTTVCLFdBQVc0QixJQUFYLEN
|