@graphql-inspector/core 2.9.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/diff/index.d.ts +2 -1
- package/diff/rules/config.d.ts +2 -0
- package/diff/rules/consider-usage.d.ts +29 -0
- package/diff/rules/index.d.ts +1 -0
- package/diff/rules/safe-unreachable.d.ts +2 -0
- package/diff/rules/types.d.ts +3 -2
- package/index.d.ts +1 -1
- package/{index.cjs.js → index.js} +155 -91
- package/{index.esm.js → index.mjs} +153 -91
- package/package.json +14 -4
- package/utils/compare.d.ts +2 -2
- package/utils/graphql.d.ts +1 -0
- package/utils/isDeprecated.d.ts +2 -0
- package/index.cjs.js.map +0 -1
- package/index.esm.js.map +0 -1
package/diff/index.d.ts
CHANGED
|
@@ -5,4 +5,5 @@ import * as rules from './rules';
|
|
|
5
5
|
export * from './rules/types';
|
|
6
6
|
export declare const DiffRule: typeof rules;
|
|
7
7
|
export * from './onComplete/types';
|
|
8
|
-
export
|
|
8
|
+
export type { UsageHandler } from './rules/consider-usage';
|
|
9
|
+
export declare function diff(oldSchema: GraphQLSchema, newSchema: GraphQLSchema, rules?: Rule[], config?: rules.ConsiderUsageConfig): Promise<Change[]>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Rule } from './types';
|
|
2
|
+
export declare type UsageHandler = (input: Array<{
|
|
3
|
+
type: string;
|
|
4
|
+
field?: string;
|
|
5
|
+
argument?: string;
|
|
6
|
+
}>) => Promise<boolean[]>;
|
|
7
|
+
export interface ConsiderUsageConfig {
|
|
8
|
+
/**
|
|
9
|
+
* Checks if it's safe to introduce a breaking change on a field
|
|
10
|
+
*
|
|
11
|
+
* Because the function is async and resolves to a boolean value
|
|
12
|
+
* you can add pretty much anything here, many different conditions or
|
|
13
|
+
* even any source of data.
|
|
14
|
+
*
|
|
15
|
+
* In the CLI we use a GraphQL endpoint with a query
|
|
16
|
+
* that checks the usage and returns stats like:
|
|
17
|
+
* min/max count and min/max precentage
|
|
18
|
+
* So we know when to allow for a breaking change.
|
|
19
|
+
*
|
|
20
|
+
* Because it returns a boolean,
|
|
21
|
+
* we can't attach any data or even customize a message of an api change.
|
|
22
|
+
* This is the first iteration, we're going to improve it soon.
|
|
23
|
+
*
|
|
24
|
+
* true - NON_BREAKING
|
|
25
|
+
* false - BREAKING
|
|
26
|
+
*/
|
|
27
|
+
checkUsage?: UsageHandler;
|
|
28
|
+
}
|
|
29
|
+
export declare const considerUsage: Rule<ConsiderUsageConfig>;
|
package/diff/rules/index.d.ts
CHANGED
package/diff/rules/types.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { GraphQLSchema } from 'graphql';
|
|
2
2
|
import { Change } from '../changes/change';
|
|
3
|
-
export declare type Rule = (input: {
|
|
3
|
+
export declare type Rule<TConfig = any> = (input: {
|
|
4
4
|
changes: Change[];
|
|
5
5
|
oldSchema: GraphQLSchema;
|
|
6
6
|
newSchema: GraphQLSchema;
|
|
7
|
-
|
|
7
|
+
config: TConfig;
|
|
8
|
+
}) => Change[] | Promise<Change[]>;
|
package/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { diff, DiffRule, Rule, CompletionArgs, CompletionHandler } from './diff';
|
|
1
|
+
export { diff, DiffRule, Rule, CompletionArgs, CompletionHandler, UsageHandler } from './diff';
|
|
2
2
|
export { validate, InvalidDocument } from './validate';
|
|
3
3
|
export { similar, SimilarMap } from './similar';
|
|
4
4
|
export * from './coverage';
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
+
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
|
|
6
|
+
|
|
7
|
+
const tslib = require('tslib');
|
|
5
8
|
const graphql = require('graphql');
|
|
9
|
+
const inspect = _interopDefault(require('object-inspect'));
|
|
6
10
|
const dependencyGraph = require('dependency-graph');
|
|
7
|
-
require('object-inspect');
|
|
8
11
|
|
|
9
12
|
function keyMap(list, keyFn) {
|
|
10
13
|
return list.reduce((map, item) => {
|
|
@@ -75,6 +78,20 @@ function compareLists(oldList, newList, callbacks) {
|
|
|
75
78
|
};
|
|
76
79
|
}
|
|
77
80
|
|
|
81
|
+
function isDeprecated(fieldOrEnumValue) {
|
|
82
|
+
var _a, _b;
|
|
83
|
+
if ('isDeprecated' in fieldOrEnumValue) {
|
|
84
|
+
return fieldOrEnumValue['isDeprecated'];
|
|
85
|
+
}
|
|
86
|
+
if (fieldOrEnumValue.deprecationReason != null) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
if ((_b = (_a = fieldOrEnumValue.astNode) === null || _a === void 0 ? void 0 : _a.directives) === null || _b === void 0 ? void 0 : _b.some(directive => directive.name.value === "deprecated")) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
78
95
|
function safeChangeForField(oldType, newType) {
|
|
79
96
|
if (!graphql.isWrappingType(oldType) && !graphql.isWrappingType(newType)) {
|
|
80
97
|
return oldType.toString() === newType.toString();
|
|
@@ -117,7 +134,7 @@ function getTypePrefix(type) {
|
|
|
117
134
|
[graphql.Kind.ENUM_TYPE_DEFINITION]: 'enum',
|
|
118
135
|
[graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION]: 'input',
|
|
119
136
|
};
|
|
120
|
-
return kindsMap[kind];
|
|
137
|
+
return kindsMap[kind.toString()];
|
|
121
138
|
}
|
|
122
139
|
function isPrimitive(type) {
|
|
123
140
|
return (['String', 'Int', 'Float', 'Boolean', 'ID'].indexOf(typeof type === 'string' ? type : type.name) !== -1);
|
|
@@ -152,7 +169,7 @@ function findDeprecatedUsages(schema, ast) {
|
|
|
152
169
|
},
|
|
153
170
|
Field(node) {
|
|
154
171
|
const fieldDef = typeInfo.getFieldDef();
|
|
155
|
-
if (fieldDef && fieldDef
|
|
172
|
+
if (fieldDef && isDeprecated(fieldDef)) {
|
|
156
173
|
const parentType = typeInfo.getParentType();
|
|
157
174
|
if (parentType) {
|
|
158
175
|
const reason = fieldDef.deprecationReason;
|
|
@@ -162,7 +179,7 @@ function findDeprecatedUsages(schema, ast) {
|
|
|
162
179
|
},
|
|
163
180
|
EnumValue(node) {
|
|
164
181
|
const enumVal = typeInfo.getEnumValue();
|
|
165
|
-
if (enumVal && enumVal
|
|
182
|
+
if (enumVal && isDeprecated(enumVal)) {
|
|
166
183
|
const type = graphql.getNamedType(typeInfo.getInputType());
|
|
167
184
|
if (type) {
|
|
168
185
|
const reason = enumVal.deprecationReason;
|
|
@@ -473,7 +490,7 @@ function enumValueRemoved(oldEnum, value) {
|
|
|
473
490
|
reason: `Removing an enum value will cause existing queries that use this enum value to error.`,
|
|
474
491
|
},
|
|
475
492
|
type: exports.ChangeType.EnumValueRemoved,
|
|
476
|
-
message: `Enum value '${value.name}' ${value
|
|
493
|
+
message: `Enum value '${value.name}' ${isDeprecated(value) ? '(deprecated) ' : ''}was removed from enum '${oldEnum.name}'`,
|
|
477
494
|
path: [oldEnum.name, value.name].join('.'),
|
|
478
495
|
};
|
|
479
496
|
}
|
|
@@ -757,7 +774,7 @@ function fieldRemoved(type, field) {
|
|
|
757
774
|
: `Removing a field is a breaking change. It is preferable to deprecate the field before removing it.`,
|
|
758
775
|
},
|
|
759
776
|
type: exports.ChangeType.FieldRemoved,
|
|
760
|
-
message: `Field '${field.name}' ${field
|
|
777
|
+
message: `Field '${field.name}' ${isDeprecated(field) ? '(deprecated) ' : ''}was removed from ${entity} '${type.name}'`,
|
|
761
778
|
path: [type.name, field.name].join('.'),
|
|
762
779
|
};
|
|
763
780
|
}
|
|
@@ -892,6 +909,73 @@ function fieldArgumentRemoved(type, field, arg) {
|
|
|
892
909
|
};
|
|
893
910
|
}
|
|
894
911
|
|
|
912
|
+
function compareTwoStrings(str1, str2) {
|
|
913
|
+
if (!str1.length && !str2.length)
|
|
914
|
+
return 1;
|
|
915
|
+
if (!str1.length || !str2.length)
|
|
916
|
+
return 0;
|
|
917
|
+
if (str1.toUpperCase() === str2.toUpperCase())
|
|
918
|
+
return 1;
|
|
919
|
+
if (str1.length === 1 && str2.length === 1)
|
|
920
|
+
return 0;
|
|
921
|
+
const pairs1 = wordLetterPairs(str1);
|
|
922
|
+
const pairs2 = wordLetterPairs(str2);
|
|
923
|
+
const union = pairs1.length + pairs2.length;
|
|
924
|
+
let intersection = 0;
|
|
925
|
+
pairs1.forEach((pair1) => {
|
|
926
|
+
for (let i = 0, pair2; (pair2 = pairs2[i]); i++) {
|
|
927
|
+
if (pair1 !== pair2)
|
|
928
|
+
continue;
|
|
929
|
+
intersection++;
|
|
930
|
+
pairs2.splice(i, 1);
|
|
931
|
+
break;
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
return (intersection * 2) / union;
|
|
935
|
+
}
|
|
936
|
+
function findBestMatch(mainString, targetStrings) {
|
|
937
|
+
if (!areArgsValid(mainString, targetStrings))
|
|
938
|
+
throw new Error('Bad arguments: First argument should be a string, second should be an array of strings');
|
|
939
|
+
const ratings = targetStrings.map((target) => ({
|
|
940
|
+
target,
|
|
941
|
+
rating: compareTwoStrings(mainString, target.value),
|
|
942
|
+
}));
|
|
943
|
+
const bestMatch = Array.from(ratings).sort((a, b) => b.rating - a.rating)[0];
|
|
944
|
+
return { ratings, bestMatch };
|
|
945
|
+
}
|
|
946
|
+
function flattenDeep(arr) {
|
|
947
|
+
return Array.isArray(arr)
|
|
948
|
+
? arr.reduce((a, b) => a.concat(flattenDeep(b)), [])
|
|
949
|
+
: [arr];
|
|
950
|
+
}
|
|
951
|
+
function areArgsValid(mainString, targetStrings) {
|
|
952
|
+
if (typeof mainString !== 'string')
|
|
953
|
+
return false;
|
|
954
|
+
if (!Array.isArray(targetStrings))
|
|
955
|
+
return false;
|
|
956
|
+
if (!targetStrings.length)
|
|
957
|
+
return false;
|
|
958
|
+
if (targetStrings.find((s) => typeof s.value !== 'string'))
|
|
959
|
+
return false;
|
|
960
|
+
return true;
|
|
961
|
+
}
|
|
962
|
+
function letterPairs(str) {
|
|
963
|
+
const pairs = [];
|
|
964
|
+
for (let i = 0, max = str.length - 1; i < max; i++)
|
|
965
|
+
pairs[i] = str.substring(i, i + 2);
|
|
966
|
+
return pairs;
|
|
967
|
+
}
|
|
968
|
+
function wordLetterPairs(str) {
|
|
969
|
+
const pairs = str.toUpperCase().split(' ').map(letterPairs);
|
|
970
|
+
return flattenDeep(pairs);
|
|
971
|
+
}
|
|
972
|
+
function safeString(obj) {
|
|
973
|
+
if (obj != null && typeof obj.toString === 'function') {
|
|
974
|
+
return `${obj}`;
|
|
975
|
+
}
|
|
976
|
+
return inspect(obj);
|
|
977
|
+
}
|
|
978
|
+
|
|
895
979
|
function fieldArgumentDescriptionChanged(type, field, oldArg, newArg) {
|
|
896
980
|
return {
|
|
897
981
|
criticality: {
|
|
@@ -910,8 +994,8 @@ function fieldArgumentDefaultChanged(type, field, oldArg, newArg) {
|
|
|
910
994
|
},
|
|
911
995
|
type: exports.ChangeType.FieldArgumentDefaultChanged,
|
|
912
996
|
message: typeof oldArg.defaultValue === 'undefined'
|
|
913
|
-
? `Default value '${newArg.defaultValue}' was added to argument '${newArg.name}' on field '${type.name}.${field.name}'`
|
|
914
|
-
: `Default value for argument '${newArg.name}' on field '${type.name}.${field.name}' changed from '${oldArg.defaultValue}' to '${newArg.defaultValue}'`,
|
|
997
|
+
? `Default value '${safeString(newArg.defaultValue)}' was added to argument '${newArg.name}' on field '${type.name}.${field.name}'`
|
|
998
|
+
: `Default value for argument '${newArg.name}' on field '${type.name}.${field.name}' changed from '${safeString(oldArg.defaultValue)}' to '${safeString(newArg.defaultValue)}'`,
|
|
915
999
|
path: [type.name, field.name, oldArg.name].join('.'),
|
|
916
1000
|
};
|
|
917
1001
|
}
|
|
@@ -966,8 +1050,8 @@ function changesInField(type, oldField, newField, addChange) {
|
|
|
966
1050
|
addChange(fieldDescriptionChanged(type, oldField, newField));
|
|
967
1051
|
}
|
|
968
1052
|
}
|
|
969
|
-
if (isNotEqual(oldField
|
|
970
|
-
if (newField
|
|
1053
|
+
if (isNotEqual(isDeprecated(oldField), isDeprecated(newField))) {
|
|
1054
|
+
if (isDeprecated(newField)) {
|
|
971
1055
|
addChange(fieldDeprecationAdded(type, newField));
|
|
972
1056
|
}
|
|
973
1057
|
else {
|
|
@@ -1108,17 +1192,21 @@ function diffSchema(oldSchema, newSchema) {
|
|
|
1108
1192
|
return changes;
|
|
1109
1193
|
}
|
|
1110
1194
|
function changesInSchema(oldSchema, newSchema, addChange) {
|
|
1195
|
+
var _a, _b, _c, _d, _e, _f;
|
|
1196
|
+
const defaultNames = {
|
|
1197
|
+
query: 'Query',
|
|
1198
|
+
mutation: 'Mutation',
|
|
1199
|
+
subscription: 'Subscription',
|
|
1200
|
+
};
|
|
1111
1201
|
const oldRoot = {
|
|
1112
|
-
query: (oldSchema.getQueryType() || {}).name,
|
|
1113
|
-
mutation: (oldSchema.getMutationType() || {}).name,
|
|
1114
|
-
subscription: (oldSchema.getSubscriptionType() || {})
|
|
1115
|
-
.name,
|
|
1202
|
+
query: (_a = (oldSchema.getQueryType() || {}).name) !== null && _a !== void 0 ? _a : defaultNames.query,
|
|
1203
|
+
mutation: (_b = (oldSchema.getMutationType() || {}).name) !== null && _b !== void 0 ? _b : defaultNames.mutation,
|
|
1204
|
+
subscription: (_c = (oldSchema.getSubscriptionType() || {}).name) !== null && _c !== void 0 ? _c : defaultNames.subscription,
|
|
1116
1205
|
};
|
|
1117
1206
|
const newRoot = {
|
|
1118
|
-
query: (newSchema.getQueryType() || {}).name,
|
|
1119
|
-
mutation: (newSchema.getMutationType() || {}).name,
|
|
1120
|
-
subscription: (newSchema.getSubscriptionType() || {})
|
|
1121
|
-
.name,
|
|
1207
|
+
query: (_d = (newSchema.getQueryType() || {}).name) !== null && _d !== void 0 ? _d : defaultNames.query,
|
|
1208
|
+
mutation: (_e = (newSchema.getMutationType() || {}).name) !== null && _e !== void 0 ? _e : defaultNames.mutation,
|
|
1209
|
+
subscription: (_f = (newSchema.getSubscriptionType() || {}).name) !== null && _f !== void 0 ? _f : defaultNames.subscription,
|
|
1122
1210
|
};
|
|
1123
1211
|
if (isNotEqual(oldRoot.query, newRoot.query)) {
|
|
1124
1212
|
addChange(schemaQueryTypeChanged(oldSchema, newSchema));
|
|
@@ -1185,7 +1273,7 @@ const suppressRemovalOfDeprecatedField = ({ changes, oldSchema, }) => {
|
|
|
1185
1273
|
const type = oldSchema.getType(typeName);
|
|
1186
1274
|
if (graphql.isObjectType(type) || graphql.isInterfaceType(type)) {
|
|
1187
1275
|
const field = type.getFields()[fieldName];
|
|
1188
|
-
if (field
|
|
1276
|
+
if (isDeprecated(field)) {
|
|
1189
1277
|
return Object.assign(Object.assign({}, change), { criticality: Object.assign(Object.assign({}, change.criticality), { level: exports.CriticalityLevel.Dangerous }) });
|
|
1190
1278
|
}
|
|
1191
1279
|
}
|
|
@@ -1197,7 +1285,7 @@ const suppressRemovalOfDeprecatedField = ({ changes, oldSchema, }) => {
|
|
|
1197
1285
|
const type = oldSchema.getType(enumName);
|
|
1198
1286
|
if (graphql.isEnumType(type)) {
|
|
1199
1287
|
const item = type.getValue(enumItem);
|
|
1200
|
-
if (item && item
|
|
1288
|
+
if (item && isDeprecated(item)) {
|
|
1201
1289
|
return Object.assign(Object.assign({}, change), { criticality: Object.assign(Object.assign({}, change.criticality), { level: exports.CriticalityLevel.Dangerous }) });
|
|
1202
1290
|
}
|
|
1203
1291
|
}
|
|
@@ -1223,21 +1311,59 @@ const ignoreDescriptionChanges = ({ changes }) => {
|
|
|
1223
1311
|
return changes.filter((change) => descriptionChangeTypes.indexOf(change.type) === -1);
|
|
1224
1312
|
};
|
|
1225
1313
|
|
|
1314
|
+
const considerUsage = ({ changes, config, }) => tslib.__awaiter(void 0, void 0, void 0, function* () {
|
|
1315
|
+
if (!config) {
|
|
1316
|
+
throw new Error(`considerUsage rule is missing config`);
|
|
1317
|
+
}
|
|
1318
|
+
const collectedBreakingField = [];
|
|
1319
|
+
changes.forEach((change) => {
|
|
1320
|
+
if (change.criticality.level === exports.CriticalityLevel.Breaking && change.path) {
|
|
1321
|
+
const [typeName, fieldName, argumentName] = parsePath(change.path);
|
|
1322
|
+
collectedBreakingField.push({
|
|
1323
|
+
type: typeName,
|
|
1324
|
+
field: fieldName,
|
|
1325
|
+
argument: argumentName,
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
});
|
|
1329
|
+
// True if safe to break, false otherwise
|
|
1330
|
+
const usageList = yield config.checkUsage(collectedBreakingField);
|
|
1331
|
+
// turns an array of booleans into an array of `Type.Field` strings
|
|
1332
|
+
// includes only those that are safe to break the api
|
|
1333
|
+
const suppressedPaths = collectedBreakingField
|
|
1334
|
+
.filter((_, i) => usageList[i] === true)
|
|
1335
|
+
.map(({ type, field, argument }) => [type, field, argument].filter(Boolean).join('.'));
|
|
1336
|
+
return changes.map((change) => {
|
|
1337
|
+
// Turns those "safe to break" changes into "dangerous"
|
|
1338
|
+
if (change.criticality.level === exports.CriticalityLevel.Breaking &&
|
|
1339
|
+
change.path &&
|
|
1340
|
+
suppressedPaths.some((p) => change.path.startsWith(p))) {
|
|
1341
|
+
return Object.assign(Object.assign({}, change), { criticality: Object.assign(Object.assign({}, change.criticality), { level: exports.CriticalityLevel.Dangerous }), message: `${change.message} (non-breaking based on usage)` });
|
|
1342
|
+
}
|
|
1343
|
+
return change;
|
|
1344
|
+
});
|
|
1345
|
+
});
|
|
1346
|
+
|
|
1226
1347
|
const rules = /*#__PURE__*/Object.freeze({
|
|
1227
1348
|
__proto__: null,
|
|
1228
1349
|
dangerousBreaking: dangerousBreaking,
|
|
1229
1350
|
suppressRemovalOfDeprecatedField: suppressRemovalOfDeprecatedField,
|
|
1230
|
-
ignoreDescriptionChanges: ignoreDescriptionChanges
|
|
1351
|
+
ignoreDescriptionChanges: ignoreDescriptionChanges,
|
|
1352
|
+
considerUsage: considerUsage
|
|
1231
1353
|
});
|
|
1232
1354
|
|
|
1233
1355
|
const DiffRule = rules;
|
|
1234
|
-
function diff(oldSchema, newSchema, rules = []) {
|
|
1356
|
+
function diff(oldSchema, newSchema, rules = [], config) {
|
|
1235
1357
|
const changes = diffSchema(oldSchema, newSchema);
|
|
1236
|
-
return rules.reduce((prev, rule) =>
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1358
|
+
return rules.reduce((prev, rule) => tslib.__awaiter(this, void 0, void 0, function* () {
|
|
1359
|
+
const prevChanges = yield prev;
|
|
1360
|
+
return rule({
|
|
1361
|
+
changes: prevChanges,
|
|
1362
|
+
oldSchema,
|
|
1363
|
+
newSchema,
|
|
1364
|
+
config,
|
|
1365
|
+
});
|
|
1366
|
+
}), Promise.resolve(changes));
|
|
1241
1367
|
}
|
|
1242
1368
|
|
|
1243
1369
|
function readDocument(source) {
|
|
@@ -1401,7 +1527,7 @@ function validate(schema, sources, options) {
|
|
|
1401
1527
|
.filter((doc) => doc.hasOperations)
|
|
1402
1528
|
.forEach((doc) => {
|
|
1403
1529
|
const docWithOperations = {
|
|
1404
|
-
kind:
|
|
1530
|
+
kind: graphql.Kind.DOCUMENT,
|
|
1405
1531
|
definitions: doc.operations.map((d) => d.node),
|
|
1406
1532
|
};
|
|
1407
1533
|
const extractedFragments = (extractFragments(graphql.print(docWithOperations)) || [])
|
|
@@ -1412,7 +1538,7 @@ function validate(schema, sources, options) {
|
|
|
1412
1538
|
// remove duplicates
|
|
1413
1539
|
.filter((def, i, all) => all.findIndex((item) => item.name.value === def.name.value) === i);
|
|
1414
1540
|
const merged = {
|
|
1415
|
-
kind:
|
|
1541
|
+
kind: graphql.Kind.DOCUMENT,
|
|
1416
1542
|
definitions: [...docWithOperations.definitions, ...extractedFragments],
|
|
1417
1543
|
};
|
|
1418
1544
|
let transformedSchema = config.apollo
|
|
@@ -1475,67 +1601,6 @@ function sumLengths(...arrays) {
|
|
|
1475
1601
|
return arrays.reduce((sum, { length }) => sum + length, 0);
|
|
1476
1602
|
}
|
|
1477
1603
|
|
|
1478
|
-
function compareTwoStrings(str1, str2) {
|
|
1479
|
-
if (!str1.length && !str2.length)
|
|
1480
|
-
return 1;
|
|
1481
|
-
if (!str1.length || !str2.length)
|
|
1482
|
-
return 0;
|
|
1483
|
-
if (str1.toUpperCase() === str2.toUpperCase())
|
|
1484
|
-
return 1;
|
|
1485
|
-
if (str1.length === 1 && str2.length === 1)
|
|
1486
|
-
return 0;
|
|
1487
|
-
const pairs1 = wordLetterPairs(str1);
|
|
1488
|
-
const pairs2 = wordLetterPairs(str2);
|
|
1489
|
-
const union = pairs1.length + pairs2.length;
|
|
1490
|
-
let intersection = 0;
|
|
1491
|
-
pairs1.forEach((pair1) => {
|
|
1492
|
-
for (let i = 0, pair2; (pair2 = pairs2[i]); i++) {
|
|
1493
|
-
if (pair1 !== pair2)
|
|
1494
|
-
continue;
|
|
1495
|
-
intersection++;
|
|
1496
|
-
pairs2.splice(i, 1);
|
|
1497
|
-
break;
|
|
1498
|
-
}
|
|
1499
|
-
});
|
|
1500
|
-
return (intersection * 2) / union;
|
|
1501
|
-
}
|
|
1502
|
-
function findBestMatch(mainString, targetStrings) {
|
|
1503
|
-
if (!areArgsValid(mainString, targetStrings))
|
|
1504
|
-
throw new Error('Bad arguments: First argument should be a string, second should be an array of strings');
|
|
1505
|
-
const ratings = targetStrings.map((target) => ({
|
|
1506
|
-
target,
|
|
1507
|
-
rating: compareTwoStrings(mainString, target.value),
|
|
1508
|
-
}));
|
|
1509
|
-
const bestMatch = Array.from(ratings).sort((a, b) => b.rating - a.rating)[0];
|
|
1510
|
-
return { ratings, bestMatch };
|
|
1511
|
-
}
|
|
1512
|
-
function flattenDeep(arr) {
|
|
1513
|
-
return Array.isArray(arr)
|
|
1514
|
-
? arr.reduce((a, b) => a.concat(flattenDeep(b)), [])
|
|
1515
|
-
: [arr];
|
|
1516
|
-
}
|
|
1517
|
-
function areArgsValid(mainString, targetStrings) {
|
|
1518
|
-
if (typeof mainString !== 'string')
|
|
1519
|
-
return false;
|
|
1520
|
-
if (!Array.isArray(targetStrings))
|
|
1521
|
-
return false;
|
|
1522
|
-
if (!targetStrings.length)
|
|
1523
|
-
return false;
|
|
1524
|
-
if (targetStrings.find((s) => typeof s.value !== 'string'))
|
|
1525
|
-
return false;
|
|
1526
|
-
return true;
|
|
1527
|
-
}
|
|
1528
|
-
function letterPairs(str) {
|
|
1529
|
-
const pairs = [];
|
|
1530
|
-
for (let i = 0, max = str.length - 1; i < max; i++)
|
|
1531
|
-
pairs[i] = str.substring(i, i + 2);
|
|
1532
|
-
return pairs;
|
|
1533
|
-
}
|
|
1534
|
-
function wordLetterPairs(str) {
|
|
1535
|
-
const pairs = str.toUpperCase().split(' ').map(letterPairs);
|
|
1536
|
-
return flattenDeep(pairs);
|
|
1537
|
-
}
|
|
1538
|
-
|
|
1539
1604
|
function similar(schema, typeName, threshold = 0.4) {
|
|
1540
1605
|
const typeMap = schema.getTypeMap();
|
|
1541
1606
|
const targets = Object.keys(schema.getTypeMap())
|
|
@@ -1680,4 +1745,3 @@ exports.diff = diff;
|
|
|
1680
1745
|
exports.getTypePrefix = getTypePrefix;
|
|
1681
1746
|
exports.similar = similar;
|
|
1682
1747
|
exports.validate = validate;
|
|
1683
|
-
//# sourceMappingURL=index.cjs.js.map
|