User:Chlod/Scripts/DuplicatedRefs.js

Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
// DuplicatedRefs
// Author: Chlod
// Version: 2.0.1

// Highlight equivalent references which are duplicated in the same paragraph.
// See [[Special:Diff/1141909502#Ref duplication highlight tool]].

mw.loader.using( [ 'mediawiki.util' ], function () {

    mw.util.addCSS( '.reference-localduplicated { background-color: #ffff00; }' );

    mw.util.addPortletLink(
        'p-tb', 'javascript:void', 'Highlight duplicate refs', 't-dupref', 
        'Highlight all references in the same paragraph which are not interrupted by other references in sequence.'
    ).addEventListener( 'click', function ( e ) {
        e.preventDefault();

        let currentReferenceBundle = [];

        let lastReferenceBundle = null;
        let $lastGrandfatherElement = null;
        let duplicatesFound = 0;

        var references = document.querySelectorAll( '.mw-body-content .mw-parser-output .reference' );
        for ( var item of references ) {
            const $item = $( item );
            const $grandfatherElement = $item.parents( '.mw-parser-output >' );
            const referenceId = $item.find( 'a' ).attr( 'href' );

            if ( referenceId == null ) {
                // Invalid reference? We'll skip it.
                continue;
            }

            if ( $lastGrandfatherElement == null ) {
                // This is only the case if this is the very first reference in the article.
                // We'll assign the reference ID as well and move on.
                $lastGrandfatherElement = $grandfatherElement;
                currentReferenceBundle.push( $item );
                continue;
            }

            if ( !$lastGrandfatherElement.is( $grandfatherElement ) ) {
                // Different paragraph/section. Snip bundle.
                $lastGrandfatherElement = $grandfatherElement;
                lastReferenceBundle = null;
                currentReferenceBundle = [];
            }

            // Checks for the next sibling. The next sibling may be a text node or a DOM element.
            const hasNextReference = item.nextSibling instanceof HTMLElement &&
                item.nextSibling.classList.contains( 'reference' );

            currentReferenceBundle.push( $item );
            if ( !hasNextReference ) {
                // Next sibling is not a reference. Snip bundle and compare.
                // Get the reference IDs and see if they are the same on both bundles.

                const bundleReferenceIds = new Set(
                    currentReferenceBundle.map(
                        e => e.find( 'a' ).attr( 'href' )
                    )
                );

                if ( lastReferenceBundle != null ) {
                    const lastBundleReferenceIds = new Set(
                        lastReferenceBundle.map(
                            e => e.find( 'a' ).attr( 'href' )
                        )
                    );

                    // Check if the two sets have the same values.
                    // Unordered, and repeating values discarded.
                    const sameIds = bundleReferenceIds.size == lastBundleReferenceIds.size &&
                        [...bundleReferenceIds].every( value => lastBundleReferenceIds.has( value ) );

                    if ( sameIds ) {
                        // Same IDs. Highlight both bundles.
                        currentReferenceBundle.forEach( e => e.addClass( 'reference-localduplicated' ) );
                        lastReferenceBundle.forEach( e => e.addClass( 'reference-localduplicated' ) );
                        duplicatesFound += currentReferenceBundle.length;
                    }
                }

                // Also check for repeated reference within the same bundle.

                if ( bundleReferenceIds.size !== currentReferenceBundle.length ) {
                    // Different IDs, but there might still be repeating references in the same bundle.
                    const foundReferenceIds = new Set();
                    const foundDuplicateReferenceIds = new Set();
                    currentReferenceBundle.forEach( e => {
                        const foundReferenceId = e.find( 'a' ).attr( 'href' );
                        if ( foundReferenceIds.has( foundReferenceId ) ) {
                            // Duplicate found!
                            foundDuplicateReferenceIds.add( foundReferenceId );
                            // Afterwards, mark the ID as found (or do nothing if it's already in).
                        }
                        // else: no duplicate found, just add it in.
                        foundReferenceIds.add( foundReferenceId );
                    } );
                    currentReferenceBundle.forEach( e => {
                        if ( foundDuplicateReferenceIds.has( e.find( 'a' ).attr( 'href' ) ) ) {
                            e.addClass( 'reference-localduplicated' );
                        }
                    } );
                    duplicatesFound += foundDuplicateReferenceIds.size;
                }

                lastReferenceBundle = currentReferenceBundle;
                currentReferenceBundle = [];
            } else {
                // Next sibling is a reference. Move on.
                continue;
            }
        }

        if ( duplicatesFound > 0 ) {
            const duplicateExample = 
                '<span class="reference-localduplicated">Duplicated references are highlighted like this.</span>';
            mw.notify(
                $.parseHTML(
                    'Found ' + duplicatesFound + ' duplicate reference pairs in the same paragraph. '
                        + duplicateExample
                ),
                { title: 'DuplicatedRefs' }
            );
        } else {
            mw.notify(
                $.parseHTML(
                    '<b>Congratulations!</b> No duplicate reference pairs within the same paragraph found.'
                ),
                { title: 'DuplicatedRefs' }
            );
        }
    } );

} );