<commit_before>// Copyright 2015 The Serulian Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package formatter

import (
	"fmt"
	"sort"
	"strings"

	"github.com/blang/semver"

	"github.com/serulian/compiler/parser"
	"github.com/serulian/compiler/vcs"
)

// emitFile emits the source code for a file.
func (sf *sourceFormatter) emitFile(node formatterNode) {
	// Emit the imports for the file.
	sf.emitImports(node)

	// Emit the module-level definitions.
	sf.emitOrderedNodes(node.getChildren(parser.NodePredicateChild))
}

// importInfo is a struct that represents the parsed information about an import.
type importInfo struct {
	node formatterNode

	source   string
	kind     string
	packages []importPackageInfo

	comparisonKey string
	sortKey       string

	isVCS      bool
	isSerulian bool
}

type importPackageInfo struct {
	subsource   string
	name        string
	packageName string
}

type byImportSortKey []importInfo

func (s byImportSortKey) Len() int {
	return len(s)
}

func (s byImportSortKey) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

func (s byImportSortKey) Less(i, j int) bool {
	return s[i].sortKey < s[j].sortKey
}

// emitImports emits the import statements for the source file.
func (sf *sourceFormatter) emitImports(node formatterNode) {
	var sortedImports = make([]importInfo, 0)
	for _, importNode := range node.getChildrenOfType(parser.NodePredicateChild, parser.NodeTypeImport) {
		// Remove any padding around the source name for VCS or non-Serulian imports.
		var source = importNode.getProperty(parser.NodeImportPredicateSource)
		if strings.HasPrefix(source, "`") || strings.HasPrefix(source, "\"") {
			source = source[1 : len(source)-1]
		}

		// Pull out the various pieces of the import.
		kind := importNode.getProperty(parser.NodeImportPredicateKind)

		// Pull out the package name(s).
		var packages = make([]importPackageInfo, 0)
		var packagesKey = ""
		for _, packageNode := range importNode.getChildren(parser.NodeImportPredicatePackageRef) {
			subsource := packageNode.getProperty(parser.NodeImportPredicateSubsource)
			name := packageNode.getProperty(parser.NodeImportPredicateName)
			packageName := packageNode.getProperty(parser.NodeImportPredicatePackageName)

			packagesKey += ":" + subsource + ":" + name + ":" + packageName

			packages = append(packages, importPackageInfo{subsource, name, packageName})
		}

		// Check for VCS and Serulian imports.
		isVCS := strings.Contains(source, "/")
		isSerulian := kind == ""

		// Determine the various runes for the sorting key.
		var vcsRune = 'a'
		if isVCS {
			vcsRune = 'z'
		}

		var serulianRune = 'z'
		if isSerulian {
			serulianRune = 'a'
		}

		var directRune = 'a'
		if len(packages) == 1 && packages[0].subsource == "" {
			directRune = 'z'
		}

		info := importInfo{
			node: importNode,

			source:   source,
			kind:     kind,
			packages: packages,

			sortKey:       fmt.Sprintf("%s/%s/%s/%s/%s/%s/%s", vcsRune, serulianRune, kind, directRune, source, packagesKey),
			comparisonKey: kind,

			isVCS:      isVCS,
			isSerulian: isSerulian,
		}

		sortedImports = append(sortedImports, info)
	}

	// Sort the imports:
	// - VCS imports (non-Serulian)
	// - VCS imports (Serulian)
	// - Local imports (non-Serulian)
	// - Local imports (Serulian)
	sort.Sort(byImportSortKey(sortedImports))

	// Emit the imports.
	sf.emitImportInfos(sortedImports)
}

// emitImportInfos emits the importInfo structs as imports.
func (sf *sourceFormatter) emitImportInfos(infos []importInfo) {
	var lastKey = ""
	for index, info := range infos {
		if index > 0 && info.comparisonKey != lastKey {
			sf.appendLine()
		}

		sf.emitImport(info)
		lastKey = info.comparisonKey
	}
}

// emitModifiedImportSource emits the source for the import,
// modified per the freezing/unfreezing options.
func (sf *sourceFormatter) emitModifiedImportSource(info importInfo) bool {
	parsed, err := vcs.ParseVCSPath(info.source)
	if err != nil {
		sf.importHandling.logError(info.node, "Could not parse VCS path '%v': %v", info.source, err)
		return false
	}

	// Check if the import's URL was specified to be modified.
	if !sf.importHandling.matchesImport(parsed.URL()) {
		return false
	}

	switch sf.importHandling.option {
	case importHandlingUnfreeze:
		// For unfreezing, append the HEAD form of the VCS path.
		sf.importHandling.logSuccess(info.node, "Unfreezing '%v'", info.source)
		sf.append(parsed.AsGeneric().String())
		return true

	case importHandlingUpdate:
		// Make sure the import refers to a tag that has a semvar.
		if parsed.Tag() == "" {
			sf.importHandling.logInfo(info.node, "Import '%v' doesn't refer to a version; skipped", info.source)
			return false
		}

		currentVersion, err := semver.ParseTolerant(parsed.Tag())
		if err != nil {
			sf.importHandling.logInfo(info.node, "Import '%v' doesn't refer to a semantic version; skipped", info.source)
			return false
		}

		// For updating, perform VCS checkout and append the latest applicable minor version of the
		// import, as per semvar. If none, then we don't change the import.
		inspectInfo, err := sf.getVCSInfo(info)
		if err != nil {
			return false
		}

		// Find the latest *minor* version, and update to it.
		currentTag := ""
		for _, tag := range inspectInfo.Tags {
			// Skip empty tags.
			if len(tag) == 0 {
				continue
			}

			// Skip tags that don't parse, as well as pre-release versions
			// (since they are, by definition, not considered stable).
			parsed, err := semver.ParseTolerant(tag)
			if err != nil || len(parsed.Pre) > 0 {
				continue
			}

			// Find the latest stable version.
			if parsed.GT(currentVersion) && parsed.Major == currentVersion.Major {
				currentVersion = parsed
				currentTag = tag
			}
		}

		if currentTag == "" {
			sf.importHandling.logInfo(info.node, "No updated version found for '%v'", info.source)
			return false
		}

		sf.importHandling.logSuccess(info.node, "Updating '%v' to version '%v'", info.source, currentTag)
		sf.append(parsed.WithTag(currentTag).String())
		return true

	case importHandlingUpgrade:
		// For upgrading, perform VCS checkout and append the latest stable version of the
		// import, as per semvar. If none, then we don't change the import.
		inspectInfo, err := sf.getVCSInfo(info)
		if err != nil {
			return false
		}

		currentVersion, _ := semver.Parse("0.0.0")
		currentTag := ""

		for _, tag := range inspectInfo.Tags {
			// Skip empty tags.
			if len(tag) == 0 {
				continue
			}

			// Skip tags that don't parse, as well as pre-release versions
			// (since they are, by definition, not considered stable).
			parsed, err := semver.ParseTolerant(tag)
			if err != nil || len(parsed.Pre) > 0 {
				continue
			}

			// Find the latest stable version.
			if parsed.GT(currentVersion) {
				currentVersion = parsed
				currentTag = tag
			}
		}

		if currentTag == "" {
			sf.importHandling.logWarning(info.node, "No stable versioned tag found for '%v'", info.source)
			return false
		}

		sf.importHandling.logSuccess(info.node, "Upgrading '%v' to version '%v'", info.source, currentTag)
		sf.append(parsed.WithTag(currentTag).String())
		return true

	case importHandlingFreeze:
		// For freezing, perform VCS checkout and append the commit of
		// the checked out info.
		inspectInfo, err := sf.getVCSInfo(info)
		if err != nil {<commit_msg>Change formatter to use new semver method in util<commit_after>// Copyright 2015 The Serulian Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package formatter

import (
	"fmt"
	"sort"
	"strings"

	"github.com/serulian/compiler/compilerutil"
	"github.com/serulian/compiler/parser"
	"github.com/serulian/compiler/vcs"
)

// emitFile emits the source code for a file.
func (sf *sourceFormatter) emitFile(node formatterNode) {
	// Emit the imports for the file.
	sf.emitImports(node)

	// Emit the module-level definitions.
	sf.emitOrderedNodes(node.getChildren(parser.NodePredicateChild))
}

// importInfo is a struct that represents the parsed information about an import.
type importInfo struct {
	node formatterNode

	source   string
	kind     string
	packages []importPackageInfo

	comparisonKey string
	sortKey       string

	isVCS      bool
	isSerulian bool
}

type importPackageInfo struct {
	subsource   string
	name        string
	packageName string
}

type byImportSortKey []importInfo

func (s byImportSortKey) Len() int {
	return len(s)
}

func (s byImportSortKey) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

func (s byImportSortKey) Less(i, j int) bool {
	return s[i].sortKey < s[j].sortKey
}

// emitImports emits the import statements for the source file.
func (sf *sourceFormatter) emitImports(node formatterNode) {
	var sortedImports = make([]importInfo, 0)
	for _, importNode := range node.getChildrenOfType(parser.NodePredicateChild, parser.NodeTypeImport) {
		// Remove any padding around the source name for VCS or non-Serulian imports.
		var source = importNode.getProperty(parser.NodeImportPredicateSource)
		if strings.HasPrefix(source, "`") || strings.HasPrefix(source, "\"") {
			source = source[1 : len(source)-1]
		}

		// Pull out the various pieces of the import.
		kind := importNode.getProperty(parser.NodeImportPredicateKind)

		// Pull out the package name(s).
		var packages = make([]importPackageInfo, 0)
		var packagesKey = ""
		for _, packageNode := range importNode.getChildren(parser.NodeImportPredicatePackageRef) {
			subsource := packageNode.getProperty(parser.NodeImportPredicateSubsource)
			name := packageNode.getProperty(parser.NodeImportPredicateName)
			packageName := packageNode.getProperty(parser.NodeImportPredicatePackageName)

			packagesKey += ":" + subsource + ":" + name + ":" + packageName

			packages = append(packages, importPackageInfo{subsource, name, packageName})
		}

		// Check for VCS and Serulian imports.
		isVCS := strings.Contains(source, "/")
		isSerulian := kind == ""

		// Determine the various runes for the sorting key.
		var vcsRune = 'a'
		if isVCS {
			vcsRune = 'z'
		}

		var serulianRune = 'z'
		if isSerulian {
			serulianRune = 'a'
		}

		var directRune = 'a'
		if len(packages) == 1 && packages[0].subsource == "" {
			directRune = 'z'
		}

		info := importInfo{
			node: importNode,

			source:   source,
			kind:     kind,
			packages: packages,

			sortKey:       fmt.Sprintf("%s/%s/%s/%s/%s/%s/%s", vcsRune, serulianRune, kind, directRune, source, packagesKey),
			comparisonKey: kind,

			isVCS:      isVCS,
			isSerulian: isSerulian,
		}

		sortedImports = append(sortedImports, info)
	}

	// Sort the imports:
	// - VCS imports (non-Serulian)
	// - VCS imports (Serulian)
	// - Local imports (non-Serulian)
	// - Local imports (Serulian)
	sort.Sort(byImportSortKey(sortedImports))

	// Emit the imports.
	sf.emitImportInfos(sortedImports)
}

// emitImportInfos emits the importInfo structs as imports.
func (sf *sourceFormatter) emitImportInfos(infos []importInfo) {
	var lastKey = ""
	for index, info := range infos {
		if index > 0 && info.comparisonKey != lastKey {
			sf.appendLine()
		}

		sf.emitImport(info)
		lastKey = info.comparisonKey
	}
}

// emitModifiedImportSource emits the source for the import,
// modified per the freezing/unfreezing options.
func (sf *sourceFormatter) emitModifiedImportSource(info importInfo) bool {
	parsed, err := vcs.ParseVCSPath(info.source)
	if err != nil {
		sf.importHandling.logError(info.node, "Could not parse VCS path '%v': %v", info.source, err)
		return false
	}

	// Check if the import's URL was specified to be modified.
	if !sf.importHandling.matchesImport(parsed.URL()) {
		return false
	}

	switch sf.importHandling.option {
	case importHandlingUnfreeze:
		// For unfreezing, append the HEAD form of the VCS path.
		sf.importHandling.logSuccess(info.node, "Unfreezing '%v'", info.source)
		sf.append(parsed.AsGeneric().String())
		return true

	case importHandlingUpdate:
		// Make sure the import refers to a tag that has a semvar.
		if parsed.Tag() == "" {
			sf.importHandling.logInfo(info.node, "Import '%v' doesn't refer to a version; skipped", info.source)
			return false
		}

		// For updating, perform VCS checkout and append the latest applicable minor version of the
		// import, as per semvar. If none, then we don't change the import.
		inspectInfo, err := sf.getVCSInfo(info)
		if err != nil {
			return false
		}

		// Find the latest *minor* version, and update to it.
		currentTag, err := compilerutil.SemanticUpdateVersion(parsed.Tag(), inspectInfo.Tags, compilerutil.UpdateVersionMinor)
		if err != nil {
			sf.importHandling.logInfo(info.node, "Import '%v' doesn't refer to a semantic version; skipped", info.source)
			return false
		}

		if currentTag == "" {
			sf.importHandling.logInfo(info.node, "No updated version found for '%v'", info.source)
			return false
		}

		sf.importHandling.logSuccess(info.node, "Updating '%v' to version '%v'", info.source, currentTag)
		sf.append(parsed.WithTag(currentTag).String())
		return true

	case importHandlingUpgrade:
		// For upgrading, perform VCS checkout and append the latest stable version of the
		// import, as per semvar. If none, then we don't change the import.
		inspectInfo, err := sf.getVCSInfo(info)
		if err != nil {
			return false
		}

		// Find the latest *major* version, and update to it.
		currentTag, err := compilerutil.SemanticUpdateVersion(parsed.Tag(), inspectInfo.Tags, compilerutil.UpdateVersionMajor)
		if err != nil {
			sf.importHandling.logInfo(info.node, "Import '%v' doesn't refer to a semantic version; skipped", info.source)
			return false
		}

		if currentTag == "" {
			sf.importHandling.logInfo(info.node, "No updated version found for '%v'", info.source)
			return false
		}

		sf.importHandling.logSuccess(info.node, "Upgrading '%v' to version '%v'", info.source, currentTag)
		sf.append(parsed.WithTag(currentTag).String())
		return true

	case importHandlingFreeze:
		// For freezing, perform VCS checkout and append the commit of
		// the checked out info.
		inspectInfo, err := sf.getVCSInfo(info)
		if err != nil {
{'fraction_non_alphanumeric': 0.11429579950582422, 'fraction_numerical': 0.0020472996823155667, 'mean_word_length': 3.689175769612711, 'pattern_counts': {'":': 6, '<': 5, '<?xml version=': 0, '>': 7, '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': '3062400', 'n_tokens_mistral': 4993, 'n_tokens_neox': 4547, 'n_words': 1337}