11/*
2+ * @flow
23 * Copyright (C) 2015 MetaBrainz Foundation
34 *
45 * This file is part of MusicBrainz, the open internet music database,
89
910import balanced from 'balanced-match' ;
1011import $ from 'jquery' ;
12+ import mutate from 'mutate-cow' ;
1113
1214import '../../common/entity.js' ;
1315
16+ import { expect } from '../../../../utility/invariant.js' ;
1417import {
1518 BRACKET_PAIRS ,
1619 MIN_NAME_SIMILARITY ,
@@ -30,8 +33,30 @@ import {
3033import getRelatedArtists from './getRelatedArtists.js' ;
3134import getSimilarity from './similarity.js' ;
3235
36+ type GuessFeatEntityT = {
37+ + artistCredit : IncompleteArtistCreditT ,
38+ + name : string ,
39+ + relationships ?: $ReadOnlyArray < RelationshipT > ,
40+ } ;
41+
42+ type GuessFeatResultT = {
43+ artistCreditNames : $ReadOnlyArray < IncompleteArtistCreditNameT > ,
44+ name : string ,
45+ } ;
46+
47+ type ExpandedArtistCreditNameT = {
48+ ...IncompleteArtistCreditNameT ,
49+ similarity : number ,
50+ } ;
51+
52+ type ExtractedCreditsT = {
53+ + artistCredit : Array < ExpandedArtistCreditNameT > ,
54+ + joinPhrase : string ,
55+ + name : string ,
56+ } ;
57+
3358/* eslint-disable sort-keys */
34- export const featRegex = / (?: ^ \s * | [ , , - - ] \s * | \s + ) ( (?: f t | f e a t | f t | f e a t ) (?: [ . . ] | (? = \s ) ) | (?: f e a t u r i n g | f e a t u r i n g ) (? = \s ) ) \s * / i;
59+ export const featRegex : RegExp = / (?: ^ \s * | [ , , - - ] \s * | \s + ) ( (?: f t | f e a t | f t | f e a t ) (?: [ . . ] | (? = \s ) ) | (?: f e a t u r i n g | f e a t u r i n g ) (? = \s ) ) \s * / i;
3560/*
3661 * `featQuickTestRegex` is used to quickly test whether a title *might*
3762 * contain featured artists. It's fine if it returns false-positives.
@@ -40,10 +65,13 @@ export const featRegex = /(?:^\s*|[,,--]\s*|\s+)((?:ft|feat|ft|fea
4065const featQuickTestRegex = / f t | f e a t | f t | f e a t / i;
4166const collabRegex = / ( [ , , ] ? \s + (?: & | a n d | e t | & | a n d | e t ) \s + | 、 | [ , , ; ; ] \s + | \s * [ / / ] \s * | \s + (?: v s | v s ) [ . . ] \s + ) / i;
4267
43- function extractNonBracketedFeatCredits ( str , artists ) {
68+ function extractNonBracketedFeatCredits (
69+ str : string ,
70+ artists : Array < ArtistT > ,
71+ ) : ExtractedCreditsT {
4472 const parts = str . split ( featRegex ) . map ( clean ) ;
4573
46- function fixFeatJoinPhrase ( existing ) {
74+ function fixFeatJoinPhrase ( existing : string ) {
4775 const joinPhrase = existing ? (
4876 ' ' +
4977 fromFullwidthLatin ( existing )
@@ -75,7 +103,10 @@ function extractNonBracketedFeatCredits(str, artists) {
75103 } ;
76104}
77105
78- function extractBracketedFeatCredits ( str , artists ) {
106+ function extractBracketedFeatCredits (
107+ str : string ,
108+ artists : Array < ArtistT > ,
109+ ) : ExtractedCreditsT {
79110 return BRACKET_PAIRS . reduce ( function ( accum , pair ) {
80111 let name = '' ;
81112 let joinPhrase = accum . joinPhrase ;
@@ -122,16 +153,18 @@ function extractBracketedFeatCredits(str, artists) {
122153}
123154
124155export function extractFeatCredits (
125- str , artists , allowEmptyName ,
126- ) {
127- if ( ! featQuickTestRegex . test ( str ) ) {
128- return { name : str , joinPhrase : '' , artistCredit : [ ] } ;
156+ name : string ,
157+ artists : Array < ArtistT > ,
158+ allowEmptyName : boolean ,
159+ ) : ExtractedCreditsT {
160+ if ( ! featQuickTestRegex . test ( name ) ) {
161+ return { name, joinPhrase : '' , artistCredit : [ ] } ;
129162 }
130163
131- const m1 = extractBracketedFeatCredits ( str , artists ) ;
164+ const m1 = extractBracketedFeatCredits ( name , artists ) ;
132165
133166 if ( ! m1 . name && ! allowEmptyName ) {
134- return { name : str , joinPhrase : '' , artistCredit : [ ] } ;
167+ return { name, joinPhrase : '' , artistCredit : [ ] } ;
135168 }
136169
137170 const m2 = extractNonBracketedFeatCredits (
@@ -149,24 +182,30 @@ export function extractFeatCredits(
149182 } ;
150183}
151184
152- function bestArtistMatch ( artists , name ) {
185+ function bestArtistMatch (
186+ artists : Array < ArtistT > | null ,
187+ name : string ,
188+ ) : ExpandedArtistCreditNameT | null {
153189 if ( ! artists ) {
154190 return null ;
155191 }
156- let match = null ;
192+ let match : ExpandedArtistCreditNameT | null = null ;
157193 for ( const artist of artists ) {
158194 const similarity = getSimilarity ( name , artist . name ) ;
159195 if (
160196 similarity >= MIN_NAME_SIMILARITY &&
161197 ( match == null || similarity > match . similarity )
162198 ) {
163- match = { similarity, artist, name} ;
199+ match = { similarity, artist, name, joinPhrase : '' } ;
164200 }
165201 }
166202 return match ;
167203}
168204
169- function expandCredit ( fullName , artists ) {
205+ function expandCredit (
206+ fullName : string ,
207+ artists : Array < ArtistT > ,
208+ ) : Array < ExpandedArtistCreditNameT > {
170209 /*
171210 * See which produces a better match to an existing artist: the full
172211 * credit, or the individual credits as split by collabRegex. Some artist
@@ -176,7 +215,7 @@ function expandCredit(fullName, artists) {
176215 */
177216 const bestFullMatch = bestArtistMatch ( artists , fullName ) ;
178217
179- function fixJoinPhrase ( existing ) {
218+ function fixJoinPhrase ( existing : string ) {
180219 const joinPhrase = ( existing || ' & ' ) ;
181220
182221 return hasFullwidthLatin ( existing )
@@ -185,12 +224,12 @@ function expandCredit(fullName, artists) {
185224 }
186225
187226 const splitParts = fullName . split ( collabRegex ) ;
188- const splitMatches = [ ] ;
189- let bestSplitMatch ;
227+ const splitMatches : Array < ExpandedArtistCreditNameT > = [];
228+ let bestSplitMatch: ExpandedArtistCreditNameT ;
190229
191230 for (let i = 0; i < splitParts . length ; i += 2 ) {
192231 const name = splitParts [ i ] ;
193- const match = {
232+ const match : ExpandedArtistCreditNameT = {
194233 similarity : - 1 ,
195234 artist : null ,
196235 name,
@@ -203,15 +242,60 @@ function expandCredit(fullName, artists) {
203242 }
204243 }
205244
206- if ( bestFullMatch && bestFullMatch . similarity > bestSplitMatch . similarity ) {
207- bestFullMatch . joinPhrase = fixJoinPhrase ( ) ;
245+ if ( bestFullMatch && bestSplitMatch &&
246+ bestFullMatch . similarity > bestSplitMatch . similarity ) {
247+ bestFullMatch . joinPhrase = fixJoinPhrase ( '' ) ;
208248 return [ bestFullMatch ] ;
209249 }
210250
211251 return splitMatches ;
212252}
213253
214- export default function guessFeat ( entity ) {
254+ export default function guessFeat (
255+ entity : GuessFeatEntityT ,
256+ ) : GuessFeatResultT | null {
257+ const name = entity . name ;
258+
259+ if ( empty ( name ) ) {
260+ // Nothing to guess from an empty name
261+ return null ;
262+ }
263+
264+ const relatedArtists = getRelatedArtists ( entity . relationships ) ;
265+
266+ const match = extractFeatCredits (
267+ name , relatedArtists , false ,
268+ ) ;
269+
270+ if ( ! match . name || ! match . artistCredit . length ) {
271+ return null ;
272+ }
273+
274+ const artistCreditNamesCtx = mutate ( entity . artistCredit . names ) ;
275+ artistCreditNamesCtx . set (
276+ entity . artistCredit . names . length - 1 ,
277+ 'joinPhrase' ,
278+ match . joinPhrase ,
279+ ) ;
280+ expect ( last ( match . artistCredit ) ) . joinPhrase = '' ;
281+
282+ for ( const name of match . artistCredit ) {
283+ artistCreditNamesCtx . write ( ) . push ( {
284+ artist : name . artist ,
285+ joinPhrase : name . joinPhrase ,
286+ name : name . name ,
287+ } ) ;
288+ }
289+
290+ return {
291+ name : match . name ,
292+ artistCreditNames : artistCreditNamesCtx . final ( ) ,
293+ } ;
294+ }
295+
296+ export function guessFeatForReleaseEditor (
297+ entity : any ,
298+ ) : any {
215299 const name = entity . name ( ) ;
216300
217301 if ( empty ( name ) ) {
@@ -250,7 +334,7 @@ export default function guessFeat(entity) {
250334}
251335
252336// For use outside of the release editor.
253- export function initGuessFeatButton ( formName ) {
337+ export function initGuessFeatButton ( formName : any ) : any {
254338 const source = MB . getSourceEntityInstance ( ) ;
255339 const augmentedEntity = Object . assign (
256340 Object . create ( source ) ,
0 commit comments