main.go GO 560 lines View on github.com → Search inside
1// SPDX-License-Identifier: MIT23package main45import (6	"errors"7	"fmt"8	"os"9	"runtime"10	"strings"1112	"github.com/boyter/scc/v3/processor"13	"github.com/spf13/cobra"14	"github.com/spf13/pflag"15)1617func printShellCompletion(cmd *cobra.Command, command string) error {18	switch command {19	case "bash":20		return cmd.GenBashCompletionV2(os.Stdout, true)21	case "zsh":22		return cmd.GenZshCompletion(os.Stdout)23	case "fish":24		return cmd.GenFishCompletion(os.Stdout, true)25	case "powershell":26		return cmd.GenPowerShellCompletion(os.Stdout)27	default:28		return errors.New("Unknown shell: " + command)29	}30}3132func printFlagSuggestion(flagSet *pflag.FlagSet, unknownFlag string) {33	flags := processor.GetMostSimilarFlags(flagSet, unknownFlag)34	if len(flags) == 0 {35		return36	}3738	if len(flags) > 1 {39		_, _ = fmt.Fprintf(os.Stderr, "The most similar flags of --%s are:\n", unknownFlag)40	} else {41		_, _ = fmt.Fprintf(os.Stderr, "The most similar flag of --%s is:\n", unknownFlag)42	}4344	for _, flag := range flags {45		_, _ = fmt.Fprintf(os.Stderr, "\t--%s\n", flag)46	}47}4849//go:generate go run scripts/include.go50func main() {51	// f, _ := os.Create("scc.pprof")52	// pprof.StartCPUProfile(f)53	// defer pprof.StopCPUProfile()5455	// Handle --mcp flag before cobra to avoid interfering with stdio56	for _, arg := range os.Args[1:] {57		if arg == "--mcp" {58			startMCPServer()59			return60		}61	}6263	if len(os.Args) == 2 && strings.HasPrefix(os.Args[1], "@") {64		// handle "scc @flags.txt" syntax65		filepath := strings.TrimPrefix(os.Args[1], "@")66		b, err := os.ReadFile(filepath)67		if err != nil {68			fmt.Printf("Error reading flags from a file: %s\n", err)69			os.Exit(1)70		}7172		args := strings.Split(string(b), "\n")73		newArgs := make([]string, 0, len(args))74		for _, x := range args {75			newArgs = append(newArgs, strings.TrimSpace(x))76		}77		os.Args = append([]string{os.Args[0]}, newArgs...)78	}7980	rootCmd := &cobra.Command{81		Use:     "scc [flags] [files or directories]",82		Short:   "scc [files or directories]",83		Long:    fmt.Sprintf("Sloc, Cloc and Code. Count lines of code in a directory with complexity estimation.\nVersion %s\nBen Boyter <ben@boyter.org> + Contributors", processor.Version),84		Version: processor.Version,85		Run: func(cmd *cobra.Command, args []string) {86			processor.DirFilePaths = args87			processor.ConfigureGc()88			processor.ConfigureLazy(true)8990			// Detect if LOCOMO price/tps flags were explicitly set91			processor.LocomoInputPriceSet = cmd.PersistentFlags().Changed("locomo-input-price")92			processor.LocomoOutputPriceSet = cmd.PersistentFlags().Changed("locomo-output-price")93			processor.LocomoTPSSet = cmd.PersistentFlags().Changed("locomo-tps")94			processor.LocomoCyclesSet = cmd.PersistentFlags().Changed("locomo-cycles")9596			processor.Process()97		},98	}99100	flags := rootCmd.PersistentFlags()101102	flags.BoolVarP(103		&processor.MaxMean,104		"character",105		"m",106		false,107		"calculate max and mean characters per line",108	)109	flags.BoolVarP(110		&processor.Percent,111		"percent",112		"p",113		false,114		"include percentage values in output",115	)116	flags.BoolVarP(117		&processor.UlocMode,118		"uloc",119		"u",120		false,121		"calculate the number of unique lines of code (ULOC) for the project",122	)123	flags.BoolVarP(124		&processor.Dryness,125		"dryness",126		"a",127		false,128		"calculate the DRYness of the project (implies --uloc)",129	)130	flags.BoolVar(131		&processor.DisableCheckBinary,132		"binary",133		false,134		"disable binary file detection",135	)136	flags.BoolVar(137		&processor.Files,138		"by-file",139		false,140		"display output for every file",141	)142	flags.BoolVar(143		&processor.Ci,144		"ci",145		false,146		"enable CI output settings where stdout is ASCII",147	)148	flags.BoolVar(149		&processor.Ignore,150		"no-ignore",151		false,152		"disables .ignore file logic",153	)154	flags.BoolVar(155		&processor.SccIgnore,156		"no-scc-ignore",157		false,158		"disables .sccignore file logic",159	)160	flags.BoolVar(161		&processor.GitIgnore,162		"no-gitignore",163		false,164		"disables .gitignore file logic",165	)166	flags.BoolVar(167		&processor.GitModuleIgnore,168		"no-gitmodule",169		false,170		"disables .gitmodules file logic",171	)172	flags.BoolVar(173		&processor.CountIgnore,174		"count-ignore",175		false,176		"set to allow .gitignore and .ignore files to be counted",177	)178	flags.BoolVar(179		&processor.Debug,180		"debug",181		false,182		"enable debug output",183	)184	flags.StringSliceVar(185		&processor.PathDenyList,186		"exclude-dir",187		[]string{".git", ".hg", ".svn"},188		"directories to exclude",189	)190	flags.IntVar(191		&processor.GcFileCount,192		"file-gc-count",193		10000,194		"number of files to parse before turning the GC on",195	)196	flags.IntVar(197		&processor.FileListQueueSize,198		"file-list-queue-size",199		runtime.NumCPU(),200		"the size of the queue of files found and ready to be read into memory",201	)202	flags.IntVar(203		&processor.FileProcessJobWorkers,204		"file-process-job-workers",205		runtime.NumCPU(),206		"number of goroutine workers that process files collecting stats",207	)208	flags.IntVar(209		&processor.FileSummaryJobQueueSize,210		"file-summary-job-queue-size",211		runtime.NumCPU(),212		"the size of the queue used to hold processed file statistics before formatting",213	)214	flags.IntVar(215		&processor.DirectoryWalkerJobWorkers,216		"directory-walker-job-workers",217		8,218		"controls the maximum number of workers which will walk the directory tree",219	)220	flags.StringVarP(221		&processor.Format,222		"format",223		"f",224		"tabular",225		"set output format [tabular, wide, json, json2, csv, csv-stream, cloc-yaml, html, html-table, sql, sql-insert, openmetrics]",226	)227	flags.StringSliceVarP(228		&processor.AllowListExtensions,229		"include-ext",230		"i",231		[]string{},232		"limit to file extensions [comma separated list: e.g. go,java,js]",233	)234	flags.StringSliceVarP(235		&processor.ExcludeListExtensions,236		"exclude-ext",237		"x",238		[]string{},239		"ignore file extensions (overrides include-ext) [comma separated list: e.g. go,java,js]",240	)241	flags.StringSliceVarP(242		&processor.ExcludeFilename,243		"exclude-file",244		"n",245		[]string{"package-lock.json", "Cargo.lock", "yarn.lock", "pubspec.lock", "Podfile.lock", "pnpm-lock.yaml"},246		"ignore files with matching names",247	)248	flags.BoolVarP(249		&processor.Languages,250		"languages",251		"l",252		false,253		"print supported languages and extensions",254	)255	flags.Int64Var(256		&processor.AverageWage,257		"avg-wage",258		56286,259		"average wage value used for basic COCOMO calculation",260	)261	flags.Float64Var(262		&processor.Overhead,263		"overhead",264		2.4,265		"set the overhead multiplier for corporate overhead (facilities, equipment, accounting, etc.)",266	)267	flags.Float64Var(268		&processor.EAF,269		"eaf",270		1.0,271		"the effort adjustment factor derived from the cost drivers (1.0 if rated nominal)",272	)273	flags.BoolVar(274		&processor.SLOCCountFormat,275		"sloccount-format",276		false,277		"print a more SLOCCount like COCOMO calculation",278	)279	flags.BoolVar(280		&processor.Cocomo,281		"no-cocomo",282		false,283		"remove COCOMO calculation output",284	)285	flags.StringVar(286		&processor.CocomoProjectType,287		"cocomo-project-type",288		"organic",289		"change COCOMO model type [organic, semi-detached, embedded, \"custom,1,1,1,1\"]",290	)291	flags.BoolVar(292		&processor.Size,293		"no-size",294		false,295		"remove size calculation output",296	)297	flags.BoolVar(298		&processor.HBorder,299		"no-hborder",300		false,301		"remove horizontal borders between sections",302	)303	flags.StringVar(304		&processor.SizeUnit,305		"size-unit",306		"si",307		"set size unit [si, binary, mixed, xkcd-kb, xkcd-kelly, xkcd-imaginary, xkcd-intel, xkcd-drive, xkcd-bakers]",308	)309	flags.BoolVarP(310		&processor.Complexity,311		"no-complexity",312		"c",313		false,314		"skip calculation of code complexity",315	)316	flags.BoolVarP(317		&processor.Duplicates,318		"no-duplicates",319		"d",320		false,321		"remove duplicate files from stats and output",322	)323	flags.BoolVarP(324		&processor.MinifiedGenerated,325		"min-gen",326		"z",327		false,328		"identify minified or generated files",329	)330	flags.BoolVarP(331		&processor.Minified,332		"min",333		"",334		false,335		"identify minified files",336	)337	flags.BoolVarP(338		&processor.Generated,339		"gen",340		"",341		false,342		"identify generated files",343	)344	flags.StringSliceVarP(345		&processor.GeneratedMarkers,346		"generated-markers",347		"",348		[]string{"do not edit", "<auto-generated />"},349		"string markers in head of generated files",350	)351	flags.BoolVar(352		&processor.IgnoreMinifiedGenerate,353		"no-min-gen",354		false,355		"ignore minified or generated files in output (implies --min-gen)",356	)357	flags.BoolVar(358		&processor.IgnoreMinified,359		"no-min",360		false,361		"ignore minified files in output (implies --min)",362	)363	flags.BoolVar(364		&processor.IgnoreGenerated,365		"no-gen",366		false,367		"ignore generated files in output (implies --gen)",368	)369	flags.IntVar(370		&processor.MinifiedGeneratedLineByteLength,371		"min-gen-line-length",372		255,373		"number of bytes per average line for file to be considered minified or generated",374	)375	flags.StringArrayVarP(376		&processor.Exclude,377		"not-match",378		`M`,379		[]string{},380		"ignore files and directories matching regular expression",381	)382	flags.StringVarP(383		&processor.FileOutput,384		"output",385		"o",386		"",387		"output filename (default stdout)",388	)389	flags.StringVarP(390		&processor.SortBy,391		"sort",392		"s",393		"files",394		"column to sort by [files, name, lines, blanks, code, comments, complexity]",395	)396	flags.BoolVarP(397		&processor.Trace,398		"trace",399		"t",400		false,401		"enable trace output (not recommended when processing multiple files)",402	)403	flags.BoolVarP(404		&processor.Verbose,405		"verbose",406		"v",407		false,408		"verbose output",409	)410	flags.BoolVarP(411		&processor.More,412		"wide",413		"w",414		false,415		"wider output with additional statistics (implies --complexity)",416	)417	flags.BoolVar(418		&processor.NoLarge,419		"no-large",420		false,421		"ignore files over certain byte and line size set by large-line-count and large-byte-count",422	)423	flags.BoolVar(424		&processor.IncludeSymLinks,425		"include-symlinks",426		false,427		"if set will count symlink files",428	)429	flags.Int64Var(430		&processor.LargeLineCount,431		"large-line-count",432		40000,433		"number of lines a file can contain before being removed from output",434	)435	flags.Int64Var(436		&processor.LargeByteCount,437		"large-byte-count",438		1000000,439		"number of bytes a file can contain before being removed from output",440	)441	flags.StringVar(442		&processor.CountAs,443		"count-as",444		"",445		"count extension as language [e.g. jsp:htm,chead:\"C Header\" maps extension jsp to html and chead to C Header]",446	)447	flags.StringVar(448		&processor.FormatMulti,449		"format-multi",450		"",451		"have multiple format output overriding --format [e.g. tabular:stdout,csv:file.csv,json:file.json]",452	)453	flags.StringVar(454		&processor.SQLProject,455		"sql-project",456		"",457		"use supplied name as the project identifier for the current run. Only valid with the --format sql or sql-insert option",458	)459	flags.StringVar(460		&processor.RemapUnknown,461		"remap-unknown",462		"",463		"inspect files of unknown type and remap by checking for a string and remapping the language [e.g. \"-*- C++ -*-\":\"C Header\"]",464	)465	flags.StringVar(466		&processor.RemapAll,467		"remap-all",468		"",469		"inspect every file and remap by checking for a string and remapping the language [e.g. \"-*- C++ -*-\":\"C Header\"]",470	)471	flags.StringVar(472		&processor.CurrencySymbol,473		"currency-symbol",474		"$",475		"set currency symbol",476	)477	flags.BoolVar(478		&processor.Locomo,479		"locomo",480		false,481		"enable LOCOMO (LLM Output COst MOdel) cost estimation",482	)483	flags.BoolVar(484		&processor.CostComparison,485		"cost-comparison",486		false,487		"show both COCOMO and LOCOMO estimates side by side",488	)489	flags.StringVar(490		&processor.LocomoPresetName,491		"locomo-preset",492		"medium",493		"LOCOMO model preset [large, medium, small, local]",494	)495	flags.Float64Var(496		&processor.LocomoReviewMinutesPerLine,497		"locomo-review",498		0.01,499		"human review minutes per line of code for LOCOMO estimate",500	)501	flags.StringVar(502		&processor.LocomoConfig,503		"locomo-config",504		"",505		"LOCOMO power-user config \"tokensPerLine,inputPerLine,complexityWeight,iterations,iterationWeight\"",506	)507	flags.Float64Var(508		&processor.LocomoInputPrice,509		"locomo-input-price",510		0,511		"LOCOMO cost per 1M input tokens in dollars (overrides preset)",512	)513	flags.Float64Var(514		&processor.LocomoOutputPrice,515		"locomo-output-price",516		0,517		"LOCOMO cost per 1M output tokens in dollars (overrides preset)",518	)519	flags.Float64Var(520		&processor.LocomoTPS,521		"locomo-tps",522		0,523		"LOCOMO output tokens per second (overrides preset)",524	)525	flags.Float64Var(526		&processor.LocomoCyclesOverride,527		"locomo-cycles",528		0,529		"override estimated LLM iteration cycles (default: calculated from complexity)",530	)531532	// --mcp is intercepted before cobra runs, but we register it here so it appears in --help533	var mcpDummy bool534	flags.BoolVar(535		&mcpDummy,536		"mcp",537		false,538		"start as an MCP (Model Context Protocol) server over stdio",539	)540541	// If invoked in the format of "scc completion --shell [name of shell]", generate command line completions instead.542	// With the --shell option, unintentionally triggering shell completions should be highly unlikely.543	args := os.Args544	if len(args) == 4 && args[1] == "completion" && args[2] == "--shell" {545		err := printShellCompletion(rootCmd, args[3])546		if err != nil {547			_, _ = fmt.Fprintf(os.Stderr, "Error printing shell completion: %s\n", err)548		}549		return550	}551552	if err := rootCmd.Execute(); err != nil {553		// If a flag does not exist and is not a shorthand, it may be a spelling error. Search for and print possible options.554		if notExistError, ok := err.(*pflag.NotExistError); ok && len(notExistError.GetSpecifiedName()) > 1 {555			printFlagSuggestion(flags, notExistError.GetSpecifiedName())556		}557		os.Exit(1)558	}559}

Code quality findings 8

Blank identifier discarding results; verify intentional ignoring of return values
warning correctness blank-identifier-discard
_, _ = fmt.Fprintf(os.Stderr, "The most similar flags of --%s are:\n", unknownFlag)
Blank identifier discarding results; verify intentional ignoring of return values
warning correctness blank-identifier-discard
_, _ = fmt.Fprintf(os.Stderr, "The most similar flag of --%s is:\n", unknownFlag)
Blank identifier discarding results; verify intentional ignoring of return values
warning correctness blank-identifier-discard
_, _ = fmt.Fprintf(os.Stderr, "\t--%s\n", flag)
Defer inside loop; deferred calls accumulate until the function returns, not until the loop iteration ends. This can cause resource leaks
warning correctness defer-in-loop
// defer pprof.StopCPUProfile()
Ensure errors are handled or logged
warning correctness unhandled-error
if err != nil {
Blank identifier discarding results; verify intentional ignoring of return values
warning correctness blank-identifier-discard
_, _ = fmt.Fprintf(os.Stderr, "Error printing shell completion: %s\n", err)
Error string starts with uppercase; per Go convention error strings should not be capitalized or end with punctuation
info maintainability error-string-format
return errors.New("Unknown shell: " + command)
Formatted output to console; prefer structured logging for consistency
info correctness fmt-printf
fmt.Printf("Error reading flags from a file: %s\n", err)

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.