eslint-plugin-unicorn
Advanced tools
Comparing version
@@ -7,4 +7,7 @@ import createDeprecatedRules from './rules/utils/create-deprecated-rules.js'; | ||
const deprecatedRules = createDeprecatedRules({ | ||
// {ruleId: ReplacementRuleId | ReplacementRuleId[]}, if no replacement, use `{ruleId: []}` | ||
'no-instanceof-array': 'unicorn/no-instanceof-builtins', | ||
// {ruleId: {message: string, replacedBy: string[]}} | ||
'no-instanceof-array': { | ||
message: 'Replaced by `unicorn/no-instanceof-builtins` which covers more cases.', | ||
replacedBy: ['unicorn/no-instanceof-builtins'], | ||
}, | ||
}); | ||
@@ -11,0 +14,0 @@ |
{ | ||
"name": "eslint-plugin-unicorn", | ||
"version": "57.0.0", | ||
"version": "58.0.0", | ||
"description": "More than 100 powerful ESLint rules", | ||
@@ -20,3 +20,3 @@ "license": "MIT", | ||
"engines": { | ||
"node": ">=18.18" | ||
"node": "^18.20.0 || ^20.10.0 || >=21.0.0" | ||
}, | ||
@@ -62,10 +62,11 @@ "scripts": { | ||
"@babel/helper-validator-identifier": "^7.25.9", | ||
"@eslint-community/eslint-utils": "^4.4.1", | ||
"ci-info": "^4.1.0", | ||
"@eslint-community/eslint-utils": "^4.5.1", | ||
"@eslint/plugin-kit": "^0.2.7", | ||
"ci-info": "^4.2.0", | ||
"clean-regexp": "^1.0.0", | ||
"core-js-compat": "^3.40.0", | ||
"core-js-compat": "^3.41.0", | ||
"esquery": "^1.6.0", | ||
"globals": "^15.15.0", | ||
"globals": "^16.0.0", | ||
"indent-string": "^5.0.0", | ||
"is-builtin-module": "^4.0.0", | ||
"is-builtin-module": "^5.0.0", | ||
"jsesc": "^3.1.0", | ||
@@ -81,19 +82,18 @@ "pluralize": "^8.0.0", | ||
"@babel/code-frame": "^7.26.2", | ||
"@babel/core": "^7.26.9", | ||
"@babel/eslint-parser": "^7.26.8", | ||
"@eslint/eslintrc": "^3.2.0", | ||
"@babel/core": "^7.26.10", | ||
"@babel/eslint-parser": "^7.26.10", | ||
"@eslint/eslintrc": "^3.3.0", | ||
"@lubien/fixture-beta-package": "^1.0.0-beta.1", | ||
"@typescript-eslint/parser": "^8.24.1", | ||
"@typescript-eslint/parser": "^8.26.1", | ||
"ava": "^6.2.0", | ||
"c8": "^10.1.3", | ||
"enquirer": "^2.4.1", | ||
"eslint": "^9.20.1", | ||
"eslint": "^9.22.0", | ||
"eslint-ava-rule-tester": "^5.0.1", | ||
"eslint-config-xo": "^0.46.0", | ||
"eslint-doc-generator": "^2.0.2", | ||
"eslint-doc-generator": "^2.1.2", | ||
"eslint-plugin-eslint-plugin": "^6.4.0", | ||
"eslint-plugin-internal-rules": "file:./scripts/internal-rules/", | ||
"eslint-plugin-jsdoc": "^50.6.3", | ||
"eslint-plugin-jsdoc": "^50.6.8", | ||
"eslint-remote-tester": "^4.0.1", | ||
"eslint-remote-tester-repositories": "^2.0.0", | ||
"eslint-remote-tester-repositories": "^2.0.1", | ||
"espree": "^10.3.0", | ||
@@ -103,3 +103,3 @@ "listr2": "^8.2.5", | ||
"markdownlint-cli": "^0.44.0", | ||
"memoize": "^10.0.0", | ||
"memoize": "^10.1.0", | ||
"nano-spawn": "^0.2.0", | ||
@@ -112,8 +112,8 @@ "node-style-text": "^0.0.7", | ||
"pretty-ms": "^9.2.0", | ||
"typescript": "^5.7.3", | ||
"vue-eslint-parser": "^9.4.3", | ||
"typescript": "^5.8.2", | ||
"vue-eslint-parser": "^10.1.1", | ||
"yaml": "^2.7.0" | ||
}, | ||
"peerDependencies": { | ||
"eslint": ">=9.20.0" | ||
"eslint": ">=9.22.0" | ||
}, | ||
@@ -120,0 +120,0 @@ "ava": { |
@@ -52,4 +52,4 @@ # eslint-plugin-unicorn [](https://codecov.io/gh/sindresorhus/eslint-plugin-unicorn/branch/main) [](https://npmjs.com/package/eslint-plugin-unicorn) | ||
💼 [Configurations](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs) enabled in.\ | ||
✅ Set in the `recommended` [configuration](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs).\ | ||
💼 [Configurations](https://github.com/sindresorhus/eslint-plugin-unicorn#recommended-config) enabled in.\ | ||
✅ Set in the `recommended` [configuration](https://github.com/sindresorhus/eslint-plugin-unicorn#recommended-config).\ | ||
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\ | ||
@@ -71,3 +71,3 @@ 💡 Manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). | ||
| [error-message](docs/rules/error-message.md) | Enforce passing a `message` value when creating a built-in error. | ✅ | | | | ||
| [escape-case](docs/rules/escape-case.md) | Require escape sequences to use uppercase values. | ✅ | 🔧 | | | ||
| [escape-case](docs/rules/escape-case.md) | Require escape sequences to use uppercase or lowercase values. | ✅ | 🔧 | | | ||
| [expiring-todo-comments](docs/rules/expiring-todo-comments.md) | Add expiration conditions to TODO comments. | ✅ | | | | ||
@@ -74,0 +74,0 @@ | [explicit-length-check](docs/rules/explicit-length-check.md) | Enforce explicitly comparing the `length` or `size` property of a value. | ✅ | 🔧 | 💡 | |
import {replaceTemplateElement} from './fix/index.js'; | ||
import {isRegexLiteral, isStringLiteral, isTaggedTemplateLiteral} from './ast/index.js'; | ||
const MESSAGE_ID = 'escape-case'; | ||
const MESSAGE_ID_UPPERCASE = 'escape-uppercase'; | ||
const MESSAGE_ID_LOWERCASE = 'escape-lowercase'; | ||
const messages = { | ||
[MESSAGE_ID]: 'Use uppercase characters for the value of the escape sequence.', | ||
[MESSAGE_ID_UPPERCASE]: 'Use uppercase characters for the value of the escape sequence.', | ||
[MESSAGE_ID_LOWERCASE]: 'Use lowercase characters for the value of the escape sequence.', | ||
}; | ||
const escapeWithLowercase = /(?<=(?:^|[^\\])(?:\\\\)*\\)(?<data>x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|u{[\dA-Fa-f]+})/g; | ||
const escapePatternWithLowercase = /(?<=(?:^|[^\\])(?:\\\\)*\\)(?<data>x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|u{[\dA-Fa-f]+}|c[a-z])/g; | ||
const getProblem = ({node, original, regex = escapeWithLowercase, fix}) => { | ||
const fixed = original.replace(regex, data => data.slice(0, 1) + data.slice(1).toUpperCase()); | ||
const escapeCase = /(?<=(?:^|[^\\])(?:\\\\)*\\)(?<data>x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|u{[\dA-Fa-f]+})/g; | ||
const escapePatternCase = /(?<=(?:^|[^\\])(?:\\\\)*\\)(?<data>x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|u{[\dA-Fa-f]+}|c[A-Za-z])/g; | ||
const getProblem = ({node, original, regex = escapeCase, lowercase, fix}) => { | ||
const fixed = original.replace(regex, data => data[0] + data.slice(1)[lowercase ? 'toLowerCase' : 'toUpperCase']()); | ||
@@ -17,3 +19,3 @@ if (fixed !== original) { | ||
node, | ||
messageId: MESSAGE_ID, | ||
messageId: lowercase ? MESSAGE_ID_LOWERCASE : MESSAGE_ID_UPPERCASE, | ||
fix: fixer => fix ? fix(fixer, fixed) : fixer.replaceText(node, fixed), | ||
@@ -26,2 +28,4 @@ }; | ||
const create = context => { | ||
const lowercase = context.options[0] === 'lowercase'; | ||
context.on('Literal', node => { | ||
@@ -32,2 +36,3 @@ if (isStringLiteral(node)) { | ||
original: node.raw, | ||
lowercase, | ||
}); | ||
@@ -42,3 +47,4 @@ } | ||
original: node.raw, | ||
regex: escapePatternWithLowercase, | ||
regex: escapePatternCase, | ||
lowercase, | ||
}); | ||
@@ -56,2 +62,3 @@ } | ||
original: node.value.raw, | ||
lowercase, | ||
fix: (fixer, fixed) => replaceTemplateElement(fixer, node, fixed), | ||
@@ -62,2 +69,8 @@ }); | ||
const schema = [ | ||
{ | ||
enum: ['uppercase', 'lowercase'], | ||
}, | ||
]; | ||
/** @type {import('eslint').Rule.RuleModule} */ | ||
@@ -69,6 +82,8 @@ const config = { | ||
docs: { | ||
description: 'Require escape sequences to use uppercase values.', | ||
description: 'Require escape sequences to use uppercase or lowercase values.', | ||
recommended: true, | ||
}, | ||
fixable: 'code', | ||
schema, | ||
defaultOptions: ['uppercase'], | ||
messages, | ||
@@ -75,0 +90,0 @@ }, |
@@ -17,3 +17,3 @@ import {getParenthesizedRange} from '../utils/parentheses.js'; | ||
tokenBefore | ||
&& range[0] === tokenBefore.range[1] | ||
&& range[0] === sourceCode.getRange(tokenBefore)[1] | ||
&& isProblematicToken(tokenBefore) | ||
@@ -28,3 +28,3 @@ ) { | ||
tokenAfter | ||
&& range[1] === tokenAfter.range[0] | ||
&& range[1] === sourceCode.getRange(tokenAfter)[0] | ||
&& isProblematicToken(tokenAfter) | ||
@@ -31,0 +31,0 @@ ) { |
export default function removeSpacesAfter(indexOrNodeOrToken, sourceCode, fixer) { | ||
let index = indexOrNodeOrToken; | ||
if (typeof indexOrNodeOrToken === 'object' && Array.isArray(indexOrNodeOrToken.range)) { | ||
index = indexOrNodeOrToken.range[1]; | ||
if (typeof indexOrNodeOrToken === 'object') { | ||
index = sourceCode.getRange(indexOrNodeOrToken)[1]; | ||
} | ||
@@ -6,0 +6,0 @@ |
@@ -25,2 +25,3 @@ import isShorthandPropertyValue from '../utils/is-shorthand-property-value.js'; | ||
return fixer.replaceTextRange( | ||
// eslint-disable-next-line internal/no-restricted-property-access | ||
[identifier.range[0], identifier.typeAnnotation.range[0]], | ||
@@ -27,0 +28,0 @@ `${replacement}${identifier.optional ? '?' : ''}`, |
@@ -6,3 +6,5 @@ // Replace `StringLiteral` or `TemplateLiteral` node with raw text | ||
[ | ||
// eslint-disable-next-line internal/no-restricted-property-access | ||
node.range[0] + 1, | ||
// eslint-disable-next-line internal/no-restricted-property-access | ||
node.range[1] - 1, | ||
@@ -9,0 +11,0 @@ ], |
const replaceTemplateElement = (fixer, node, replacement) => { | ||
// eslint-disable-next-line internal/no-restricted-property-access | ||
const {range: [start, end], tail} = node; | ||
@@ -3,0 +4,0 @@ return fixer.replaceTextRange( |
@@ -0,1 +1,3 @@ | ||
import {ConfigCommentParser} from '@eslint/plugin-kit'; | ||
const MESSAGE_ID = 'no-abusive-eslint-disable'; | ||
@@ -6,4 +8,10 @@ const messages = { | ||
const disableRegex = /^eslint-disable(?:-next-line|-line)?(?<ruleId>$|(?:\s+(?:@(?:[\w-]+\/){1,2})?[\w-]+)?)/; | ||
// https://github.com/eslint/eslint/blob/ecd0ede7fd2ccbb4c0daf0e4732e97ea0f49db1b/lib/linter/linter.js#L509-L512 | ||
const eslintDisableDirectives = new Set([ | ||
'eslint-disable', | ||
'eslint-disable-line', | ||
'eslint-disable-next-line', | ||
]); | ||
let commentParser; | ||
/** @param {import('eslint').Rule.RuleContext} context */ | ||
@@ -13,24 +21,28 @@ const create = context => ({ | ||
for (const comment of node.comments) { | ||
const value = comment.value.trim(); | ||
const result = disableRegex.exec(value); | ||
commentParser ??= new ConfigCommentParser(); | ||
const result = commentParser.parseDirective(comment.value); | ||
if ( | ||
result // It's a eslint-disable comment | ||
&& !result.groups.ruleId // But it did not specify any rules | ||
) { | ||
const {sourceCode} = context; | ||
if (!( | ||
// It's a eslint-disable comment | ||
eslintDisableDirectives.has(result?.label) | ||
// But it did not specify any rules | ||
&& !result?.value | ||
)) { | ||
return; | ||
} | ||
yield { | ||
// Can't set it at the given location as the warning | ||
// will be ignored due to the disable comment | ||
loc: { | ||
start: { | ||
...sourceCode.getLoc(comment).start, | ||
column: -1, | ||
}, | ||
end: sourceCode.getLoc(comment).end, | ||
const {sourceCode} = context; | ||
yield { | ||
// Can't set it at the given location as the warning | ||
// will be ignored due to the disable comment | ||
loc: { | ||
start: { | ||
...sourceCode.getLoc(comment).start, | ||
column: -1, | ||
}, | ||
messageId: MESSAGE_ID, | ||
}; | ||
} | ||
end: sourceCode.getLoc(comment).end, | ||
}, | ||
messageId: MESSAGE_ID, | ||
}; | ||
} | ||
@@ -37,0 +49,0 @@ }, |
@@ -41,3 +41,3 @@ const MESSAGE_ID_ERROR = 'no-accessor-recursion/error'; | ||
const isValidProperty = property => | ||
['Property', 'MethodDefinition'].includes(property.type) | ||
['Property', 'MethodDefinition'].includes(property?.type) | ||
&& !property.computed | ||
@@ -44,0 +44,0 @@ && ['set', 'get'].includes(property.kind) |
@@ -156,3 +156,3 @@ import {isSemicolonToken} from '@eslint-community/eslint-utils'; | ||
&& sourceCode.getLoc(body).start.line !== sourceCode.getLoc(parent).start.line | ||
&& sourceCode.text.slice(classToken.range[1], body.range[0]).trim() | ||
&& sourceCode.text.slice(sourceCode.getRange(classToken)[1], sourceCode.getRange(body)[0]).trim() | ||
) { | ||
@@ -159,0 +159,0 @@ yield fixer.replaceText(classToken, '{'); |
@@ -16,12 +16,5 @@ import {getStaticValue, getPropertyName} from '@eslint-community/eslint-utils'; | ||
const isPropertyThen = (node, context) => { | ||
// `getPropertyName` throws on `({[Symbol.prototype]: 0})` | ||
// https://github.com/eslint-community/eslint-utils/pull/182 | ||
try { | ||
return getPropertyName(node, context.sourceCode.getScope(node)) === 'then'; | ||
} catch {} | ||
const isPropertyThen = (node, context) => | ||
getPropertyName(node, context.sourceCode.getScope(node)) === 'then'; | ||
return false; | ||
}; | ||
const cases = [ | ||
@@ -28,0 +21,0 @@ // `{then() {}}`, |
@@ -71,4 +71,4 @@ import path from 'node:path'; | ||
const {browserlist, engines} = packageResult.packageJson; | ||
return browserlist ?? engines; | ||
const {browserslist, engines} = packageResult.packageJson; | ||
return browserslist ?? engines; | ||
} | ||
@@ -131,6 +131,5 @@ | ||
const [, namespace, method = ''] = polyfill.feature.split('.'); | ||
const [, features] = Object.entries(coreJsEntries).find( | ||
entry => entry[0] === `core-js/full/${namespace}${method && '/'}${method}`, | ||
); | ||
if (checkFeatures(features)) { | ||
const features = coreJsEntries[`core-js/full/${namespace}${method && '/'}${method}`]; | ||
if (features && checkFeatures(features)) { | ||
return {node, messageId: MESSAGE_ID_POLYFILL}; | ||
@@ -137,0 +136,0 @@ } |
@@ -9,6 +9,10 @@ import {checkVueTemplate} from './utils/rule.js'; | ||
const fix = raw => { | ||
/** | ||
@param {string} raw | ||
@param {Options} options | ||
*/ | ||
const fix = (raw, {hexadecimalValue}) => { | ||
let fixed = raw.toLowerCase(); | ||
if (fixed.startsWith('0x')) { | ||
fixed = '0x' + fixed.slice(2).toUpperCase(); | ||
fixed = '0x' + fixed.slice(2)[hexadecimalValue === 'lowercase' ? 'toLowerCase' : 'toUpperCase'](); | ||
} | ||
@@ -20,11 +24,15 @@ | ||
/** @param {import('eslint').Rule.RuleContext} context */ | ||
const create = () => ({ | ||
const create = context => ({ | ||
Literal(node) { | ||
const {raw} = node; | ||
/** @type {Options} */ | ||
const options = context.options[0] ?? {}; | ||
options.hexadecimalValue ??= 'uppercase'; | ||
let fixed = raw; | ||
if (isNumberLiteral(node)) { | ||
fixed = fix(raw); | ||
fixed = fix(raw, options); | ||
} else if (isBigIntLiteral(node)) { | ||
fixed = fix(raw.slice(0, -1)) + 'n'; | ||
fixed = fix(raw.slice(0, -1), options) + 'n'; | ||
} | ||
@@ -42,2 +50,18 @@ | ||
/** @typedef {Record<keyof typeof schema[0]["properties"], typeof caseEnum["enum"][number]>} Options */ | ||
const caseEnum = /** @type {const} */ ({ | ||
enum: ['uppercase', 'lowercase'], | ||
}); | ||
const schema = [ | ||
{ | ||
type: 'object', | ||
additionalProperties: false, | ||
properties: { | ||
hexadecimalValue: caseEnum, | ||
}, | ||
}, | ||
]; | ||
/** @type {import('eslint').Rule.RuleModule} */ | ||
@@ -53,2 +77,6 @@ const config = { | ||
fixable: 'code', | ||
schema, | ||
defaultOptions: [{ | ||
hexadecimalValue: 'uppercase', | ||
}], | ||
messages, | ||
@@ -55,0 +83,0 @@ }, |
@@ -200,5 +200,5 @@ import { | ||
&& tokenBefore.value === '-' | ||
&& /^\s+$/.test(sourceCode.text.slice(tokenBefore.range[1], numberNode.range[0])) | ||
&& /^\s+$/.test(sourceCode.text.slice(sourceCode.getRange(tokenBefore)[1], sourceCode.getRange(numberNode)[0])) | ||
) { | ||
yield fixer.removeRange([tokenBefore.range[1], numberNode.range[0]]); | ||
yield fixer.removeRange([sourceCode.getRange(tokenBefore)[1], sourceCode.getRange(numberNode)[0]]); | ||
} | ||
@@ -279,3 +279,3 @@ } | ||
const [, start] = getParenthesizedRange(sliceCall.arguments[0], sourceCode); | ||
const [end] = sourceCode.getLastToken(sliceCall).range; | ||
const [end] = sourceCode.getRange(sourceCode.getLastToken(sliceCall)); | ||
yield fixer.removeRange([start, end]); | ||
@@ -282,0 +282,0 @@ } |
@@ -8,2 +8,3 @@ import isBuiltinModule from 'is-builtin-module'; | ||
}; | ||
const NODE_PROTOCOL = 'node:'; | ||
@@ -31,8 +32,8 @@ const create = context => ({ | ||
if ( | ||
typeof value !== 'string' | ||
|| value.startsWith('node:') | ||
|| /^bun(?::|$)/.test(value) | ||
|| !isBuiltinModule(value) | ||
) { | ||
if (!( | ||
typeof value === 'string' | ||
&& !value.startsWith(NODE_PROTOCOL) | ||
&& isBuiltinModule(value) | ||
&& isBuiltinModule(`${NODE_PROTOCOL}${value}`) | ||
)) { | ||
return; | ||
@@ -47,3 +48,3 @@ } | ||
/** @param {import('eslint').Rule.RuleFixer} fixer */ | ||
fix: fixer => fixer.insertTextAfterRange([insertPosition, insertPosition], 'node:'), | ||
fix: fixer => fixer.insertTextAfterRange([insertPosition, insertPosition], NODE_PROTOCOL), | ||
}; | ||
@@ -50,0 +51,0 @@ }, |
@@ -101,2 +101,3 @@ import {hasSideEffect} from '@eslint-community/eslint-utils'; | ||
const isNodeInsideNode = (inner, outer) => | ||
// eslint-disable-next-line internal/no-restricted-property-access | ||
inner.range[0] >= outer.range[0] && inner.range[1] <= outer.range[1]; | ||
@@ -103,0 +104,0 @@ function hasBreakInside(breakStatements, node) { |
@@ -255,2 +255,4 @@ /* eslint sort-keys: ["error", "asc", {"caseSensitive": false}] */ | ||
getStaticProps: true, | ||
// The name iOS is a standard name for an OS | ||
iOS: true, | ||
// React PropTypes | ||
@@ -257,0 +259,0 @@ // https://reactjs.org/docs/typechecking-with-proptypes.html |
@@ -6,18 +6,25 @@ import packageJson from '../../package.json' with {type: 'json'}; | ||
/** @returns {{ [ruleName: string]: import('eslint').Rule.RuleModule }} */ | ||
export default function createDeprecatedRules(data) { | ||
export default function createDeprecatedRules(rules) { | ||
return Object.fromEntries( | ||
Object.entries(data).map(([ruleId, replacedBy = []]) => [ | ||
ruleId, | ||
{ | ||
create: () => ({}), | ||
meta: { | ||
docs: { | ||
url: `${repoUrl}/blob/v${packageJson.version}/docs/deprecated-rules.md#${ruleId}`, | ||
Object.entries(rules).map(([ruleId, deprecatedInfo]) => { | ||
const url = `${repoUrl}/blob/v${packageJson.version}/docs/deprecated-rules.md#${ruleId}`; | ||
return [ | ||
ruleId, | ||
{ | ||
create: () => ({}), | ||
meta: { | ||
docs: { | ||
description: deprecatedInfo.message, | ||
url, | ||
}, | ||
deprecated: { | ||
message: deprecatedInfo.message, | ||
url, | ||
replacedBy: deprecatedInfo.replacedBy, | ||
}, | ||
}, | ||
deprecated: true, | ||
replacedBy: Array.isArray(replacedBy) ? replacedBy : [replacedBy], | ||
}, | ||
}, | ||
]), | ||
]; | ||
}), | ||
); | ||
} |
const hasSameRange = (node1, node2) => | ||
node1 | ||
&& node2 | ||
// eslint-disable-next-line internal/no-restricted-property-access | ||
&& node1.range[0] === node2.range[0] | ||
// eslint-disable-next-line internal/no-restricted-property-access | ||
&& node1.range[1] === node2.range[1]; | ||
export default hasSameRange; |
@@ -49,4 +49,4 @@ import {isParenthesized, isOpeningParenToken, isClosingParenToken} from '@eslint-community/eslint-utils'; | ||
const parentheses = getParentheses(node, sourceCode); | ||
const [start] = (parentheses[0] || node).range; | ||
const [, end] = (parentheses.at(-1) || node).range; | ||
const [start] = sourceCode.getRange(parentheses[0] ?? node); | ||
const [, end] = sourceCode.getRange(parentheses.at(-1) ?? node); | ||
return [start, end]; | ||
@@ -53,0 +53,0 @@ } |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
747461
0.27%32
-3.03%23247
0.25%18
5.88%+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
Updated
Updated
Updated
Updated