<commit_before>module ImportJS
  # Class that represents an import statement, e.g.
  # `const foo = require('foo');`
  # `var foo = myCustomRequire('foo');`
  # `import foo from 'foo';`
  class ImportStatement
    REGEX_CONST_LET_VAR = /
      \A
      (?<declaration_keyword>const|let|var)\s+ # <declaration_keyword>
      (?<assignment>.+?)   # <assignment> variable assignment
      \s*=\s*
      (?<import_function>\w+?)\( # <import_function> variable assignment
        (?<quote>'|")      # <quote> opening quote
        (?<path>[^\2\n]+)  # <path> module path
        \k<quote>          # closing quote
      \);?
      \s*
      \Z
    /xm

    REGEX_IMPORT = /
      \A
      (?<declaration_keyword>import)\s+ # <declaration_keyword>
      (?<assignment>.*?) # <assignment> variable assignment
      \s+from\s+
      (?<quote>'|")      # <quote> opening quote
      (?<path>[^\2\n]+)  # <path> module path
      \k<quote>          # closing quote
      ;?\s*
      \Z
    /xm

    REGEX_NAMED = /
      (?:                # non-capturing group
        (?<default>.*?)  # <default> default import
        ,\s*
      )?
      \{
        \s*
        (?<named>.*)      # <named> named imports
        \s*
      \}
    /xm

    attr_accessor :assignment
    attr_accessor :declaration_keyword
    attr_accessor :default_import
    attr_accessor :named_imports
    attr_accessor :import_function
    attr_accessor :original_import_string # a cache of the parsed import string
    attr_accessor :path

    # @param string [String] a possible import statement, e.g.
    #   `const foo = require('foo');`
    #   `var foo = myCustomRequire('foo');`
    #   `import foo from 'foo';`
    # @return [ImportJS::ImportStatement?] a parsed statement, or nil if the
    #   string can't be parsed
    def self.parse(string)
      match = REGEX_CONST_LET_VAR.match(string) ||
              REGEX_IMPORT.match(string)
      return unless match

      statement = new
      statement.original_import_string = match.string
      statement.declaration_keyword = match[:declaration_keyword]
      statement.path = match[:path]
      statement.assignment = match[:assignment]
      statement.import_function = if match.names.include? 'import_function'
                                    match[:import_function]
                                  else
                                    'require'
                                  end

      dest_match = statement.assignment.match(REGEX_NAMED)
      if dest_match
        statement.default_import = dest_match[:default]
        statement.named_imports =
          dest_match[:named].split(/,\s*/).map(&:strip)
      else
        statement.default_import = statement.assignment
      end

      statement
    end

    # Sets the default_import and clears the original import string cache.
    # @param value [String]
    def set_default_import(value)
      @default_import = value
      clear_import_string_cache
    end

    # Injects a new variable into an already existing set of named imports.
    # @param variable_name [String]
    def inject_named_import(variable_name)
      @named_imports ||= []
      named_imports << variable_name
      named_imports.sort!.uniq!

      clear_import_string_cache
    end

    # Deletes a variable from an already existing default import or set of
    # named imports.
    # @param variable_name [String]
    def delete_variable!(variable_name)
      @default_import = nil if default_import == variable_name
      @named_imports.delete(variable_name) if named_imports?

      clear_import_string_cache
    end

    # @return [Boolean] true if there are named imports
    def named_imports?
      !named_imports.nil? && !named_imports.empty?
    end

    # @return [Boolean] true if there is no default import and there are no
    #   named imports
    def empty?
      default_import.nil? && !named_imports?
    end

    # @return [Boolean] true if this instance was created through parsing an
    #   existing import and it hasn't been altered since it was created.
    def parsed_and_untouched?
      !original_import_string.nil?
    end

    # @return [Array] an array that can be used in `sort` and `uniq`
    def to_normalized
      [default_import || '', named_imports || '']
    end

    # @return [Array<String>] Array of all variables that this ImportStatement
    #   imports.
    def variables
      [@default_import, *@named_imports].compact
    end

    # @param max_line_length [Number] where to cap lines at
    # @param tab [String] e.g. '  ' (two spaces)
    # @return [Array] generated import statement strings
    def to_import_strings(max_line_length, tab)
      return [original_import_string] if original_import_string

      if declaration_keyword == 'import'
        # ES2015 Modules (ESM) syntax can support default imports and
        # named imports on the same line.
        if named_imports?
          [named_import_string(max_line_length, tab)]
        else
          [default_import_string(max_line_length, tab)]
        end
      else # const/var
        strings = []

        if default_import
          strings << default_import_string(max_line_length, tab)
        end

        if named_imports?
          strings << named_import_string(max_line_length, tab)
        end

        strings
      end
    end
<commit_msg>Tighten up ImportStatement#to_import_strings<commit_after>module ImportJS
  # Class that represents an import statement, e.g.
  # `const foo = require('foo');`
  # `var foo = myCustomRequire('foo');`
  # `import foo from 'foo';`
  class ImportStatement
    REGEX_CONST_LET_VAR = /
      \A
      (?<declaration_keyword>const|let|var)\s+ # <declaration_keyword>
      (?<assignment>.+?)   # <assignment> variable assignment
      \s*=\s*
      (?<import_function>\w+?)\( # <import_function> variable assignment
        (?<quote>'|")      # <quote> opening quote
        (?<path>[^\2\n]+)  # <path> module path
        \k<quote>          # closing quote
      \);?
      \s*
      \Z
    /xm

    REGEX_IMPORT = /
      \A
      (?<declaration_keyword>import)\s+ # <declaration_keyword>
      (?<assignment>.*?) # <assignment> variable assignment
      \s+from\s+
      (?<quote>'|")      # <quote> opening quote
      (?<path>[^\2\n]+)  # <path> module path
      \k<quote>          # closing quote
      ;?\s*
      \Z
    /xm

    REGEX_NAMED = /
      (?:                # non-capturing group
        (?<default>.*?)  # <default> default import
        ,\s*
      )?
      \{
        \s*
        (?<named>.*)      # <named> named imports
        \s*
      \}
    /xm

    attr_accessor :assignment
    attr_accessor :declaration_keyword
    attr_accessor :default_import
    attr_accessor :named_imports
    attr_accessor :import_function
    attr_accessor :original_import_string # a cache of the parsed import string
    attr_accessor :path

    # @param string [String] a possible import statement, e.g.
    #   `const foo = require('foo');`
    #   `var foo = myCustomRequire('foo');`
    #   `import foo from 'foo';`
    # @return [ImportJS::ImportStatement?] a parsed statement, or nil if the
    #   string can't be parsed
    def self.parse(string)
      match = REGEX_CONST_LET_VAR.match(string) ||
              REGEX_IMPORT.match(string)
      return unless match

      statement = new
      statement.original_import_string = match.string
      statement.declaration_keyword = match[:declaration_keyword]
      statement.path = match[:path]
      statement.assignment = match[:assignment]
      statement.import_function = if match.names.include? 'import_function'
                                    match[:import_function]
                                  else
                                    'require'
                                  end

      dest_match = statement.assignment.match(REGEX_NAMED)
      if dest_match
        statement.default_import = dest_match[:default]
        statement.named_imports =
          dest_match[:named].split(/,\s*/).map(&:strip)
      else
        statement.default_import = statement.assignment
      end

      statement
    end

    # Sets the default_import and clears the original import string cache.
    # @param value [String]
    def set_default_import(value)
      @default_import = value
      clear_import_string_cache
    end

    # Injects a new variable into an already existing set of named imports.
    # @param variable_name [String]
    def inject_named_import(variable_name)
      @named_imports ||= []
      named_imports << variable_name
      named_imports.sort!.uniq!

      clear_import_string_cache
    end

    # Deletes a variable from an already existing default import or set of
    # named imports.
    # @param variable_name [String]
    def delete_variable!(variable_name)
      @default_import = nil if default_import == variable_name
      @named_imports.delete(variable_name) if named_imports?

      clear_import_string_cache
    end

    # @return [Boolean] true if there are named imports
    def named_imports?
      !named_imports.nil? && !named_imports.empty?
    end

    # @return [Boolean] true if there is no default import and there are no
    #   named imports
    def empty?
      default_import.nil? && !named_imports?
    end

    # @return [Boolean] true if this instance was created through parsing an
    #   existing import and it hasn't been altered since it was created.
    def parsed_and_untouched?
      !original_import_string.nil?
    end

    # @return [Array] an array that can be used in `sort` and `uniq`
    def to_normalized
      [default_import || '', named_imports || '']
    end

    # @return [Array<String>] Array of all variables that this ImportStatement
    #   imports.
    def variables
      [@default_import, *@named_imports].compact
    end

    # @param max_line_length [Number] where to cap lines at
    # @param tab [String] e.g. '  ' (two spaces)
    # @return [Array<String>] generated import statement strings
    def to_import_strings(max_line_length, tab)
      return [original_import_string] if original_import_string

      if declaration_keyword == 'import'
        # ES2015 Modules (ESM) syntax can support default imports and
        # named imports on the same line.
        return [named_import_string(max_line_length, tab)] if named_imports?
        [default_import_string(max_line_length, tab)]
      else # const/var
        strings = []
        strings << default_import_string(max_line_length, tab) if default_import
        strings << named_import_string(max_line_length, tab) if named_imports?
        strings
      end
    end

{'fraction_non_alphanumeric': 0.09779743975903614, 'fraction_numerical': 0.0011295180722891566, 'mean_word_length': 2.38483593501115, 'pattern_counts': {'":': 0, '<': 66, '<?xml version=': 0, '>': 54, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2343078', 'n_tokens_mistral': 3183, 'n_tokens_neox': 3074, 'n_words': 821}