Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit cdeedfd

Browse files
committedFeb 10, 2022
Add no-unknown-locale rule
1 parent 2870269 commit cdeedfd

File tree

14 files changed

+630
-1
lines changed

14 files changed

+630
-1
lines changed
 

‎docs/rules/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
| [@intlify/vue-i18n/<wbr>no-duplicate-keys-in-locale](./no-duplicate-keys-in-locale.html) | disallow duplicate localization keys within the same locale | |
2828
| [@intlify/vue-i18n/<wbr>no-dynamic-keys](./no-dynamic-keys.html) | disallow localization dynamic keys at localization methods | |
2929
| [@intlify/vue-i18n/<wbr>no-missing-keys-in-other-locales](./no-missing-keys-in-other-locales.html) | disallow missing locale message keys in other locales | |
30+
| [@intlify/vue-i18n/<wbr>no-unknown-locale](./no-unknown-locale.html) | disallow unknown locale name | |
3031
| [@intlify/vue-i18n/<wbr>no-unused-keys](./no-unused-keys.html) | disallow unused localization keys | :black_nib: |
3132
| [@intlify/vue-i18n/<wbr>prefer-sfc-lang-attr](./prefer-sfc-lang-attr.html) | require lang attribute on `<i18n>` block | :black_nib: |
3233

‎docs/rules/no-unknown-locale.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
---
2+
title: '@intlify/vue-i18n/no-unknown-locale'
3+
description: disallow unknown locale name
4+
---
5+
6+
# @intlify/vue-i18n/no-unknown-locale
7+
8+
> disallow unknown locale name
9+
10+
## :book: Rule Details
11+
12+
This rule reports the use of unknown locale names.
13+
14+
By default, this rule only commonly known locale names specified in [RFC 5646] are allowed.
15+
The rule uses the [is-language-code] package to check if the locale name is compatible with [RFC 5646].
16+
17+
[rfc 5646]: https://datatracker.ietf.org/doc/html/rfc5646
18+
[is-language-code]: https://www.npmjs.com/package/is-language-code
19+
20+
<eslint-code-block>
21+
22+
<!-- eslint-skip -->
23+
24+
```vue
25+
<script>
26+
/* eslint @intlify/vue-i18n/no-unknown-locale: "error" */
27+
</script>
28+
29+
<!-- ✓ GOOD -->
30+
<i18n locale="en">
31+
{
32+
"hello": "Hello!"
33+
}
34+
</i18n>
35+
36+
<!-- ✗ BAD -->
37+
<i18n locale="foo">
38+
{
39+
"hello": "Foo!"
40+
}
41+
</i18n>
42+
```
43+
44+
</eslint-code-block>
45+
46+
## :gear: Options
47+
48+
```json
49+
{
50+
"@intlify/vue-i18n/no-unknown-locale": [
51+
"error",
52+
{
53+
"locales": [],
54+
"disableRFC5646": false
55+
}
56+
]
57+
}
58+
```
59+
60+
- `locales` ... Specify the locale names you want to use specially in an array. The rule excludes the specified name from the check.
61+
- `disableRFC5646` ... If `true`, only the locale names listed in `locales` are allowed.
62+
63+
## :mag: Implementation
64+
65+
- [Rule source](https://github.com/intlify/eslint-plugin-vue-i18n/blob/master/lib/rules/no-unknown-locale.ts)
66+
- [Test source](https://github.com/intlify/eslint-plugin-vue-i18n/tree/master/tests/lib/rules/no-unknown-locale.ts)

‎lib/rules.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import noI18nTPathProp from './rules/no-i18n-t-path-prop'
1010
import noMissingKeysInOtherLocales from './rules/no-missing-keys-in-other-locales'
1111
import noMissingKeys from './rules/no-missing-keys'
1212
import noRawText from './rules/no-raw-text'
13+
import noUnknownLocale from './rules/no-unknown-locale'
1314
import noUnusedKeys from './rules/no-unused-keys'
1415
import noVHtml from './rules/no-v-html'
1516
import preferLinkedKeyWithParen from './rules/prefer-linked-key-with-paren'
@@ -28,6 +29,7 @@ export = {
2829
'no-missing-keys-in-other-locales': noMissingKeysInOtherLocales,
2930
'no-missing-keys': noMissingKeys,
3031
'no-raw-text': noRawText,
32+
'no-unknown-locale': noUnknownLocale,
3133
'no-unused-keys': noUnusedKeys,
3234
'no-v-html': noVHtml,
3335
'prefer-linked-key-with-paren': preferLinkedKeyWithParen,

‎lib/rules/no-unknown-locale.ts

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
import type { AST as JSONAST } from 'jsonc-eslint-parser'
2+
import type { AST as YAMLAST } from 'yaml-eslint-parser'
3+
import type { AST as VAST } from 'vue-eslint-parser'
4+
import { extname } from 'path'
5+
import { isLangCode } from 'is-language-code'
6+
import debugBuilder from 'debug'
7+
import type { RuleContext, RuleListener } from '../types'
8+
import { createRule } from '../utils/rule'
9+
import {
10+
getLocaleMessages,
11+
defineCustomBlocksVisitor,
12+
getAttribute
13+
} from '../utils/index'
14+
import type { LocaleMessage } from '../utils/locale-messages'
15+
const debug = debugBuilder('eslint-plugin-vue-i18n:no-unknown-locale')
16+
17+
function create(context: RuleContext): RuleListener {
18+
const filename = context.getFilename()
19+
const locales: string[] = context.options[0]?.locales || []
20+
const disableRFC5646 = context.options[0]?.disableRFC5646 || false
21+
22+
function verifyLocaleCode(
23+
locale: string,
24+
reportNode: JSONAST.JSONNode | YAMLAST.YAMLNode | VAST.VAttribute | null
25+
) {
26+
if (locales.includes(locale)) {
27+
return
28+
}
29+
if (!disableRFC5646 && isLangCode(locale).res) {
30+
return
31+
}
32+
context.report({
33+
message: "'{{locale}}' is unknown locale name",
34+
data: {
35+
locale
36+
},
37+
loc: reportNode?.loc || { line: 1, column: 0 }
38+
})
39+
}
40+
41+
function createVerifyContext<N extends JSONAST.JSONNode | YAMLAST.YAMLNode>(
42+
targetLocaleMessage: LocaleMessage,
43+
block: VAST.VElement | null
44+
) {
45+
type KeyStack =
46+
| {
47+
locale: null
48+
node?: N
49+
upper?: KeyStack
50+
}
51+
| {
52+
locale: string
53+
node?: N
54+
upper?: KeyStack
55+
}
56+
let keyStack: KeyStack
57+
if (targetLocaleMessage.isResolvedLocaleByFileName()) {
58+
const locale = targetLocaleMessage.locales[0]
59+
keyStack = {
60+
locale
61+
}
62+
verifyLocaleCode(locale, block && getAttribute(block, 'locale'))
63+
} else {
64+
keyStack = {
65+
locale: null
66+
}
67+
}
68+
69+
// localeMessages.locales
70+
return {
71+
enterKey(key: string | number, node: N) {
72+
if (keyStack.locale == null) {
73+
const locale = String(key)
74+
keyStack = {
75+
node,
76+
locale,
77+
upper: keyStack
78+
}
79+
verifyLocaleCode(locale, node)
80+
} else {
81+
keyStack = {
82+
node,
83+
locale: keyStack.locale,
84+
upper: keyStack
85+
}
86+
}
87+
},
88+
leaveKey(node: N | null) {
89+
if (keyStack.node === node) {
90+
keyStack = keyStack.upper!
91+
}
92+
}
93+
}
94+
}
95+
96+
/**
97+
* Create node visitor for JSON
98+
*/
99+
function createVisitorForJson(
100+
targetLocaleMessage: LocaleMessage,
101+
block: VAST.VElement | null
102+
): RuleListener {
103+
const ctx = createVerifyContext(targetLocaleMessage, block)
104+
return {
105+
JSONProperty(node: JSONAST.JSONProperty) {
106+
const key =
107+
node.key.type === 'JSONLiteral' ? `${node.key.value}` : node.key.name
108+
109+
ctx.enterKey(key, node.key)
110+
},
111+
'JSONProperty:exit'(node: JSONAST.JSONProperty) {
112+
ctx.leaveKey(node.key)
113+
},
114+
'JSONArrayExpression > *'(
115+
node: JSONAST.JSONArrayExpression['elements'][number] & {
116+
parent: JSONAST.JSONArrayExpression
117+
}
118+
) {
119+
const key = node.parent.elements.indexOf(node)
120+
ctx.enterKey(key, node)
121+
},
122+
'JSONArrayExpression > *:exit'(
123+
node: JSONAST.JSONArrayExpression['elements'][number]
124+
) {
125+
ctx.leaveKey(node)
126+
}
127+
}
128+
}
129+
130+
/**
131+
* Create node visitor for YAML
132+
*/
133+
function createVisitorForYaml(
134+
targetLocaleMessage: LocaleMessage,
135+
block: VAST.VElement | null
136+
): RuleListener {
137+
const yamlKeyNodes = new Set<YAMLAST.YAMLContent | YAMLAST.YAMLWithMeta>()
138+
139+
function withinKey(node: YAMLAST.YAMLNode) {
140+
for (const keyNode of yamlKeyNodes) {
141+
if (
142+
keyNode.range[0] <= node.range[0] &&
143+
node.range[0] < keyNode.range[1]
144+
) {
145+
return true
146+
}
147+
}
148+
return false
149+
}
150+
151+
const ctx = createVerifyContext(targetLocaleMessage, block)
152+
153+
return {
154+
YAMLPair(node: YAMLAST.YAMLPair) {
155+
if (node.key != null) {
156+
if (withinKey(node)) {
157+
return
158+
}
159+
yamlKeyNodes.add(node.key)
160+
}
161+
162+
if (node.key != null && node.key.type === 'YAMLScalar') {
163+
const keyValue = node.key.value
164+
const key = typeof keyValue === 'string' ? keyValue : String(keyValue)
165+
166+
ctx.enterKey(key, node.key)
167+
}
168+
},
169+
'YAMLPair:exit'(node: YAMLAST.YAMLPair) {
170+
if (node.key != null) {
171+
ctx.leaveKey(node.key)
172+
}
173+
},
174+
'YAMLSequence > *'(
175+
node: YAMLAST.YAMLSequence['entries'][number] & {
176+
parent: YAMLAST.YAMLSequence
177+
}
178+
) {
179+
if (withinKey(node)) {
180+
return
181+
}
182+
const key = node.parent.entries.indexOf(node)
183+
ctx.enterKey(key, node)
184+
},
185+
'YAMLSequence > *:exit'(node: YAMLAST.YAMLSequence['entries'][number]) {
186+
ctx.leaveKey(node)
187+
}
188+
}
189+
}
190+
191+
if (extname(filename) === '.vue') {
192+
return defineCustomBlocksVisitor(
193+
context,
194+
ctx => {
195+
const localeMessages = getLocaleMessages(context)
196+
const targetLocaleMessage = localeMessages.findBlockLocaleMessage(
197+
ctx.parserServices.customBlock
198+
)
199+
if (!targetLocaleMessage) {
200+
return {}
201+
}
202+
return createVisitorForJson(
203+
targetLocaleMessage,
204+
ctx.parserServices.customBlock
205+
)
206+
},
207+
ctx => {
208+
const localeMessages = getLocaleMessages(context)
209+
const targetLocaleMessage = localeMessages.findBlockLocaleMessage(
210+
ctx.parserServices.customBlock
211+
)
212+
if (!targetLocaleMessage) {
213+
return {}
214+
}
215+
return createVisitorForYaml(
216+
targetLocaleMessage,
217+
ctx.parserServices.customBlock
218+
)
219+
}
220+
)
221+
} else if (context.parserServices.isJSON || context.parserServices.isYAML) {
222+
const localeMessages = getLocaleMessages(context)
223+
const targetLocaleMessage = localeMessages.findExistLocaleMessage(filename)
224+
if (!targetLocaleMessage) {
225+
debug(`ignore ${filename} in no-unknown-locale`)
226+
return {}
227+
}
228+
229+
if (context.parserServices.isJSON) {
230+
return createVisitorForJson(targetLocaleMessage, null)
231+
} else if (context.parserServices.isYAML) {
232+
return createVisitorForYaml(targetLocaleMessage, null)
233+
}
234+
return {}
235+
} else {
236+
debug(`ignore ${filename} in no-unknown-locale`)
237+
return {}
238+
}
239+
}
240+
241+
export = createRule({
242+
meta: {
243+
type: 'suggestion',
244+
docs: {
245+
description: 'disallow unknown locale name',
246+
category: 'Best Practices',
247+
url: 'https://eslint-plugin-vue-i18n.intlify.dev/rules/no-unknown-locale.html',
248+
recommended: false
249+
},
250+
fixable: null,
251+
schema: [
252+
{
253+
type: 'object',
254+
properties: {
255+
locales: {
256+
type: 'array',
257+
items: { type: 'string' }
258+
},
259+
disableRFC5646: { type: 'boolean' }
260+
},
261+
additionalProperties: false
262+
}
263+
]
264+
},
265+
create
266+
})

‎lib/utils.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import * as localeMessages from './utils/locale-messages'
1515
import * as parsers from './utils/parsers'
1616
import * as pathUtils from './utils/path-utils'
1717
import * as resourceLoader from './utils/resource-loader'
18+
import * as rule from './utils/rule'
1819

1920
export = {
2021
'cache-function': cacheFunction,
@@ -32,5 +33,6 @@ export = {
3233
'locale-messages': localeMessages,
3334
parsers,
3435
'path-utils': pathUtils,
35-
'resource-loader': resourceLoader
36+
'resource-loader': resourceLoader,
37+
rule
3638
}

‎package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"debug": "^4.3.1",
3131
"glob": "^7.1.3",
3232
"ignore": "^5.0.5",
33+
"is-language-code": "^3.1.0",
3334
"js-yaml": "^4.0.0",
3435
"json5": "^2.1.3",
3536
"jsonc-eslint-parser": "^2.0.0",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
null
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
null
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
null
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
null
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
null
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
null

‎tests/lib/rules/no-unknown-locale.ts

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
import { join } from 'path'
2+
import { RuleTester } from 'eslint'
3+
import rule = require('../../../lib/rules/no-unknown-locale')
4+
import type { SettingsVueI18nLocaleDirObject } from '../../../lib/types'
5+
const vueParser = require.resolve('vue-eslint-parser')
6+
const jsonParser = require.resolve('jsonc-eslint-parser')
7+
const yamlParser = require.resolve('yaml-eslint-parser')
8+
const fileLocalesRoot = join(__dirname, '../../fixtures/no-unknown-locale/file')
9+
const keyLocalesRoot = join(__dirname, '../../fixtures/no-unknown-locale/key')
10+
11+
const options = {
12+
json: {
13+
fileTest: {
14+
parser: jsonParser,
15+
filename: join(fileLocalesRoot, 'test.json'),
16+
settings: {
17+
'vue-i18n': {
18+
localeDir: fileLocalesRoot + '/*.{json,yaml,yml}'
19+
}
20+
}
21+
},
22+
fileEn: {
23+
parser: jsonParser,
24+
filename: join(fileLocalesRoot, 'en.json'),
25+
settings: {
26+
'vue-i18n': {
27+
localeDir: fileLocalesRoot + '/*.{json,yaml,yml}'
28+
}
29+
}
30+
},
31+
key: {
32+
parser: jsonParser,
33+
filename: join(keyLocalesRoot, 'test.json'),
34+
settings: {
35+
'vue-i18n': {
36+
localeDir: {
37+
pattern: keyLocalesRoot + '/*.{json,yaml,yml}',
38+
localeKey: 'key'
39+
} as SettingsVueI18nLocaleDirObject
40+
}
41+
}
42+
}
43+
},
44+
yaml: {
45+
fileTest: {
46+
parser: yamlParser,
47+
filename: join(fileLocalesRoot, 'test.yaml'),
48+
settings: {
49+
'vue-i18n': {
50+
localeDir: fileLocalesRoot + '/*.{json,yaml,yml}'
51+
}
52+
}
53+
},
54+
fileEn: {
55+
parser: yamlParser,
56+
filename: join(fileLocalesRoot, 'en.yaml'),
57+
settings: {
58+
'vue-i18n': {
59+
localeDir: fileLocalesRoot + '/*.{json,yaml,yml}'
60+
}
61+
}
62+
},
63+
key: {
64+
parser: yamlParser,
65+
filename: join(keyLocalesRoot, 'test.yaml'),
66+
settings: {
67+
'vue-i18n': {
68+
localeDir: {
69+
pattern: keyLocalesRoot + '/*.{json,yaml,yml}',
70+
localeKey: 'key'
71+
} as SettingsVueI18nLocaleDirObject
72+
}
73+
}
74+
}
75+
}
76+
}
77+
78+
const tester = new RuleTester({
79+
parser: vueParser,
80+
parserOptions: {
81+
ecmaVersion: 2020,
82+
sourceType: 'module'
83+
}
84+
})
85+
86+
tester.run('no-unknown-locale', rule as never, {
87+
valid: [
88+
{
89+
filename: 'test.vue',
90+
code: `
91+
<i18n locale="en">
92+
{"msg_key":{}}
93+
</i18n>
94+
<i18n locale="ja">
95+
{"msg_key":{}}
96+
</i18n>
97+
<i18n locale="i-enochian">
98+
{"msg_key":{}}
99+
</i18n>
100+
<i18n locale="zh">
101+
{"msg_key":{}}
102+
</i18n>
103+
<i18n locale="zh-Hant">
104+
{"msg_key":{}}
105+
</i18n>
106+
<i18n>
107+
{
108+
"sr-Latn-CS": {"msg_key":{}},
109+
"en-US": {"msg_key":{}}
110+
}
111+
</i18n>
112+
`
113+
},
114+
{
115+
code: `{"msg_key":{}}`,
116+
...options.yaml.fileEn
117+
},
118+
{
119+
code: `{"msg_key":{}}`,
120+
...options.json.fileEn
121+
},
122+
{
123+
code: `{"en":{"msg_key":{}}}`,
124+
...options.yaml.key
125+
},
126+
{
127+
code: `{"en":{"msg_key":{}}}`,
128+
...options.json.key
129+
},
130+
{
131+
filename: 'test.vue',
132+
code: `
133+
<i18n locale="easy_ja">
134+
</i18n>
135+
<i18n>
136+
{
137+
"easy_ja": {}
138+
}
139+
</i18n>
140+
`,
141+
options: [{ locales: ['easy_ja'] }]
142+
},
143+
{
144+
filename: 'test.vue',
145+
code: `
146+
<i18n locale="easy_ja">
147+
</i18n>
148+
<i18n>
149+
{
150+
"easy_ja": {}
151+
}
152+
</i18n>
153+
`,
154+
options: [{ locales: ['easy_ja'], disableRFC5646: true }]
155+
}
156+
],
157+
invalid: [
158+
{
159+
filename: 'test.vue',
160+
code: `
161+
<i18n locale="unknown">
162+
</i18n>
163+
<i18n locale="zh-Hant">
164+
</i18n>
165+
<i18n locale="ja-foo">
166+
</i18n>
167+
<i18n>
168+
{
169+
"foo": {},
170+
"en": {},
171+
"b-ar": {}
172+
}
173+
</i18n>
174+
`,
175+
errors: [
176+
{
177+
message: "'unknown' is unknown locale name",
178+
line: 2,
179+
column: 13
180+
},
181+
{
182+
message: "'ja-foo' is unknown locale name",
183+
line: 6,
184+
column: 13
185+
},
186+
{
187+
message: "'foo' is unknown locale name",
188+
line: 10,
189+
column: 9
190+
},
191+
{
192+
message: "'b-ar' is unknown locale name",
193+
line: 12,
194+
column: 9
195+
}
196+
]
197+
},
198+
{
199+
code: `{"msg_key":{}}`,
200+
...options.yaml.fileTest,
201+
errors: [
202+
{
203+
message: "'test' is unknown locale name",
204+
line: 1,
205+
column: 1
206+
}
207+
]
208+
},
209+
{
210+
code: `{"msg_key":{}}`,
211+
...options.json.fileTest,
212+
errors: [
213+
{
214+
message: "'test' is unknown locale name",
215+
line: 1,
216+
column: 1
217+
}
218+
]
219+
},
220+
{
221+
code: `{"foo":{"msg_key":{}}}`,
222+
...options.yaml.key,
223+
errors: [
224+
{
225+
message: "'foo' is unknown locale name",
226+
line: 1,
227+
column: 2
228+
}
229+
]
230+
},
231+
{
232+
code: `{"foo":{"msg_key":{}}}`,
233+
...options.json.key,
234+
errors: [
235+
{
236+
message: "'foo' is unknown locale name",
237+
line: 1,
238+
column: 2
239+
}
240+
]
241+
},
242+
{
243+
filename: 'test.vue',
244+
code: `
245+
<i18n locale="easy_ja">
246+
</i18n>
247+
<i18n locale="en">
248+
</i18n>
249+
<i18n>
250+
{
251+
"easy_ja": {},
252+
"en": {},
253+
}
254+
</i18n>
255+
`,
256+
options: [{ locales: ['easy_ja'], disableRFC5646: true }],
257+
errors: [
258+
{
259+
message: "'en' is unknown locale name",
260+
line: 4,
261+
column: 13
262+
},
263+
{
264+
message: "'en' is unknown locale name",
265+
line: 9,
266+
column: 9
267+
}
268+
]
269+
}
270+
]
271+
})

‎yarn.lock

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,13 @@
879879
dependencies:
880880
regenerator-runtime "^0.13.4"
881881

882+
"@babel/runtime@^7.14.0":
883+
version "7.17.2"
884+
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941"
885+
integrity sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==
886+
dependencies:
887+
regenerator-runtime "^0.13.4"
888+
882889
"@babel/template@^7.0.0", "@babel/template@^7.16.0":
883890
version "7.16.0"
884891
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.0.tgz#d16a35ebf4cd74e202083356fab21dd89363ddd6"
@@ -6018,6 +6025,13 @@ is-lambda@^1.0.1:
60186025
resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5"
60196026
integrity sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=
60206027

6028+
is-language-code@^3.1.0:
6029+
version "3.1.0"
6030+
resolved "https://registry.yarnpkg.com/is-language-code/-/is-language-code-3.1.0.tgz#b2386b49227e7010636f16d0c2c681ca40136ab5"
6031+
integrity sha512-zJdQ3QTeLye+iphMeK3wks+vXSRFKh68/Pnlw7aOfApFSEIOhYa8P9vwwa6QrImNNBMJTiL1PpYF0f4BxDuEgA==
6032+
dependencies:
6033+
"@babel/runtime" "^7.14.0"
6034+
60216035
is-negative-zero@^2.0.1:
60226036
version "2.0.1"
60236037
resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"

0 commit comments

Comments
 (0)
Please sign in to comment.