<commit_before>'use babel'

/* @flow */

import {CompositeDisposable, Emitter, Range} from 'atom'
import debounce from 'sb-debounce'
import {messageKey} from './helpers'
import type {Disposable, TextBuffer} from 'atom'

type Linter$Message = Object
type Linter$Linter = Object
type Linter$Message$Map = {
  buffer: ?TextBuffer,
  linter: Linter$Linter,
  changed: boolean,
  messages: Array<Linter$Message>,
  oldMessages: Array<Linter$Message>
}
type Linter$MessageRegistry$Result = {
  added: Array<Linter$Message>,
  removed: Array<Linter$Message>,
  messages: Array<Linter$Message>
}

export class MessageRegistry {
  emitter: Emitter;
  messages: Array<Linter$Message>;
  messagesMap: Set<Linter$Message$Map>;
  subscriptions: CompositeDisposable;
  debouncedUpdate: (() => void);

  constructor() {
    this.emitter = new Emitter()
    this.messages = []
    this.messagesMap = new Set()
    this.subscriptions = new CompositeDisposable()
    this.debouncedUpdate = debounce(this.update, 100, true)

    this.subscriptions.add(this.emitter)
  }
  set({messages, linter, buffer}: Linter$Message$Map) {
    let found = null
    for (const entry of this.messagesMap) {
      if (entry.buffer === buffer && entry.linter === linter) {
        found = entry
        break
      }
    }

    if (found) {
      found.oldMessages = found.messages
      found.messages = messages
      found.changed = true
    } else {
      this.messagesMap.add({messages, linter, buffer, oldMessages: [], changed: true})
    }
    this.debouncedUpdate()
  }
  update() {
    const result = {added: [], removed: [], messages: []}

    for (const entry of this.messagesMap) {
      if (!entry.changed) {
        result.messages = result.messages.concat(entry.oldMessages)
        continue
      }
      entry.changed = false
      if (!entry.oldMessages.length) {
        // All messages are new, no need to diff
        result.added = result.added.concat(entry.messages)
        result.messages = result.messages.concat(entry.messages)
        for (const message of entry.messages) {
          message.name = message.name || entry.linter.name || null
        }
        continue
      }
      if (!entry.messages.length) {
        // All messages are old, no need to diff
        result.removed = result.removed.concat(entry.oldMessages)
        continue
      }

      const newKeys = new Set()
      const oldKeys = new Set()
      let foundNew = false

      for (const message of entry.oldMessages) {
        oldKeys.add(message.key)
      }

      for (const message of entry.messages) {
        message.name = message.name || entry.linter.name || null
        message.key = messageKey(message)
        newKeys.add(message.key)
        if (!oldKeys.has(message.key)) {
          foundNew = true
          result.added.push(message)
          result.messages.push(message)
        }
      }

      if (!foundNew && entry.messages.length === entry.oldMessages.length) {
        // Messages are unchanged
        result.messages = result.messages.concat(entry.oldMessages)
        continue
      }

      for (const message of entry.oldMessages) {
        if (newKeys.has(message.key)) {
          result.messages.push(message)
        } else {
          result.removed.push(message)
        }
      }<commit_msg>:bug: Fix another state bug in message registry<commit_after>'use babel'

/* @flow */

import {CompositeDisposable, Emitter, Range} from 'atom'
import debounce from 'sb-debounce'
import {messageKey} from './helpers'
import type {Disposable, TextBuffer} from 'atom'

type Linter$Message = Object
type Linter$Linter = Object
type Linter$Message$Map = {
  buffer: ?TextBuffer,
  linter: Linter$Linter,
  changed: boolean,
  messages: Array<Linter$Message>,
  oldMessages: Array<Linter$Message>
}
type Linter$MessageRegistry$Result = {
  added: Array<Linter$Message>,
  removed: Array<Linter$Message>,
  messages: Array<Linter$Message>
}

export class MessageRegistry {
  emitter: Emitter;
  messages: Array<Linter$Message>;
  messagesMap: Set<Linter$Message$Map>;
  subscriptions: CompositeDisposable;
  debouncedUpdate: (() => void);

  constructor() {
    this.emitter = new Emitter()
    this.messages = []
    this.messagesMap = new Set()
    this.subscriptions = new CompositeDisposable()
    this.debouncedUpdate = debounce(this.update, 100, true)

    this.subscriptions.add(this.emitter)
  }
  set({messages, linter, buffer}: Linter$Message$Map) {
    let found = null
    for (const entry of this.messagesMap) {
      if (entry.buffer === buffer && entry.linter === linter) {
        found = entry
        break
      }
    }

    if (found) {
      found.oldMessages = found.messages
      found.messages = messages
      found.changed = true
    } else {
      this.messagesMap.add({messages, linter, buffer, oldMessages: [], changed: true})
    }
    this.debouncedUpdate()
  }
  update() {
    const result = {added: [], removed: [], messages: []}

    for (const entry of this.messagesMap) {
      if (!entry.changed) {
        result.messages = result.messages.concat(entry.oldMessages)
        continue
      }
      const oldMessages = entry.oldMessages
      entry.changed = false
      entry.oldMessages = entry.messages
      if (!oldMessages.length) {
        // All messages are new, no need to diff
        result.added = result.added.concat(entry.messages)
        result.messages = result.messages.concat(entry.messages)
        for (const message of entry.messages) {
          message.name = message.name || entry.linter.name || null
        }
        continue
      }
      if (!entry.messages.length) {
        // All messages are old, no need to diff
        result.removed = result.removed.concat(oldMessages)
        continue
      }

      const newKeys = new Set()
      const oldKeys = new Set()
      let foundNew = false

      for (const message of oldMessages) {
        oldKeys.add(message.key)
      }

      for (const message of entry.messages) {
        message.name = message.name || entry.linter.name || null
        message.key = messageKey(message)
        newKeys.add(message.key)
        if (!oldKeys.has(message.key)) {
          foundNew = true
          result.added.push(message)
          result.messages.push(message)
        }
      }

      if (!foundNew && entry.messages.length === oldMessages.length) {
        // Messages are unchanged
        result.messages = result.messages.concat(oldMessages)
        continue
      }

      for (const message of oldMessages) {
        if (newKeys.has(message.key)) {
          result.messages.push(message)
        } else {
          result.removed.push(message)
        }
      }
{'fraction_non_alphanumeric': 0.11580524344569289, 'fraction_numerical': 0.0008988764044943821, 'mean_word_length': 2.5969827586206895, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5777496', 'n_tokens_mistral': 1975, 'n_tokens_neox': 1931, 'n_words': 460}