Skip to content

Unified Parser

The Unified Parser measures source code on its own, without any other tool: complexity, real lines of code, comments, functions and a few code smells, per file and per function. It reads 20 languages and frameworks with Tree-sitter grammars and is the parser behind most CodeCharta maps.

Terminal window
ccsh unifiedparser path/to/project -o project.cc.json

This writes project.cc.json.gz. Add -nc for an uncompressed file.

ParameterDescription
FOLDER or FILEThe project folder or code file to parse. To merge the result with an existing project piped into STDIN, pass a ’-’ as an additional argument
-bf, --base-file=<baseFile>base cc.json file with checksums to skip unchanged files during analysis
--bypass-gitignoredisable automatic .gitignore-based file exclusion (uses regex-based exclusion of common build folders)
-e, --exclude=<exclude>comma-separated list of regex patterns to exclude files/folders (applied in addition to .gitignore patterns)
-fe, --file-extensions=<fileExtensions>comma-separated list of file-extensions to parse only those files (default: any)
--commit=<ref>analyze the codebase at a specific git commit, tag, branch, or date expression (creates a temporary worktree). Cannot be combined with --local-changes. See Commit-Based Analysis
-h, --helpdisplays this help and exits
-ibf, --include-build-foldersinclude build folders (out, build, dist and target) and common resource folders (e.g. resources, node_modules or files/folders starting with ’.’)
--local-changesonly analyze files that differ from the remote tracking branch (uncommitted, staged, unstaged, untracked). See Local Changes
-nc, --not-compressedsave uncompressed output File
-o, --output-file=<outputFile>output File (or empty for stdout)
--verbosedisplays messages about parsed and ignored files
Usage: ccsh unifiedparser [-h] [--bypass-gitignore] [--commit=<ref>] [-ibf]
[--local-changes] [-nc] [--verbose]
[-bf=<baseFile>] [-o=<outputFile>]
[-e=<specifiedExcludePatterns>]...
[-fe=<fileExtensionsToAnalyse>]... FILE or FOLDER...
LanguageSupported file extensions
Javascript.js, .cjs, .mjs, .jsx
Typescript.ts, .cts, .mts
TSX.tsx
Java.java
Kotlin.kt, .kts
C#.cs
C++.cpp, .cc, .cxx, .c++, .hh, .hpp, .hxx
C.c, .h
Objective-C.m
Python.py
Go.go
PHP.php
Ruby.rb
Swift.swift
Bash.sh
Vue.vue
Delphi.pas, .dpr
Rust.rs
Angular/HTML.html
SQL.sql, .pks, .pkb, .pls

Some languages need a word of explanation:

  • Vue: the parser measures the first <script> block of a .vue file with the JavaScript rules. The template and the styles count for no metric, loc and rloc included.
  • Angular templates: every .html file is read as an Angular template, by this parser as by dependencyparser and domainlanguageparser. Exclude the .html files that are no templates of an Angular application, such as reports or documentation, with -e. The inline template of a @Component in a .ts file is measured as well, and its complexity adds to the complexity and logic complexity of the .ts file.
  • SQL: one grammar covers ANSI SQL, T-SQL, PL/SQL, PL/pgSQL and MySQL, so no dialect has to be chosen.
MetricDescription
complexityComplexity of the file based on the number of paths through the code. Also includes complexity introduced by definition of functions, classes, etc. (Represents the ‘cognitive load’ necessary to overlook the whole file)
logic_complexityComplexity of the file based on number of paths through the code, similar to cyclomatic complexity (only counts complexity in code, not complexity introduced by definition of functions, classes, etc.)
comment_linesThe number of comment lines found in a file
number_of_functionsThe number of functions and methods in a file
loc (Lines of Code)Lines of code including empty lines and comments
rloc (Real lines of code)Number of lines that contain at least one character which is neither a whitespace nor a tabulation nor part of a comment
long_methodCode smell showing the number of functions with more than 10 real lines of code (rloc)
long_parameter_listCode smell showing the number of functions with more than 4 parameters
excessive_commentsCode smell showing whether a file has more than 10 comment lines
comment_ratioThe ratio of comment lines to real lines of code (rloc)
message_chainsCode smell showing occurrences of method call chains with 4 or more consecutive calls suggesting tight coupling
number_of_stored_proceduresOnly in SQL files: the stored procedures the file defines with a body, on their own or in an Oracle package or type body
number_of_triggersOnly in SQL files: the triggers the file creates, with a body or calling a function
number_of_viewsOnly in SQL files: the views the file defines, materialized ones included, temporary ones (TEMP VIEW) not
number_of_tablesOnly in SQL files: the tables the file creates, Oracle’s global temporary tables included, without the temporary tables of a session (#name, TEMP, GLOBAL TEMP, PRIVATE TEMPORARY)

Some metrics are calculated per function. For each of them, a file gets the maximum, minimum, mean and median over its functions, as max_, min_, mean_ and median_ metrics, such as max_complexity_per_function.

Metric per functionDescription
parameters_per_functionThe number of parameters for each function
complexity_per_functionThe complexity inside the body of a function
rloc_per_functionThe real lines of code inside the body of a function

A function nested in another one, such as a local function or a method of an object expression, is a function of its own: its parameters, complexity and lines count for it and not for the enclosing function.

The four SQL metrics count the distinct objects a file defines:

  • Bodies: a procedure declared in an Oracle package specification counts where its body is, in the package body, like number_of_functions counts only routines with a body. A specification and its body in one file count each procedure once; in two files only the body’s file counts it.
  • ALTER: a T-SQL ALTER PROCEDURE, ALTER VIEW or ALTER TRIGGER with a body defines the object as much as CREATE does and counts. Oracle’s ALTER PROCEDURE p COMPILE or ALTER TRIGGER t DISABLE does not.
  • Duplicates: an object created and altered, or created twice, counts once.
  • Schemas: objects of one name in two schemas (public.orders, audit.orders) count twice. One written without a schema is the object of that name in a schema when the file holds the name in exactly one schema (dbo.p1, p1).
  • Names compare as the Dependency Parser compares them, so "Orders" and orders are two tables.
  • Triggers: a PostgreSQL trigger counts per table it is on, as PostgreSQL names triggers per table.
  • Overloads: a procedure of one name in two packages or types counts twice, and so do overloads, routines of one name with other parameter types (p(a NUMBER), p(a VARCHAR2), p(int), p(text)). Parameter modes and default values make no overload, and neither do T-SQL’s parameters, as T-SQL has no overloads. A MySQL procedure dropped and created again with other parameters counts twice, as its parameters can not be told from PostgreSQL’s.
  • Runtime objects: what a routine creates when it runs, a table or a local procedure, counts for none of them. What a script creates in an anonymous block, a PostgreSQL DO block or a T-SQL IF … BEGIN … END, counts like any of its objects.

The parser reads each file with Tree-sitter and counts specific syntax nodes per metric and language.

Complexity follows McCabe complexity and counts the paths through the code. These constructs add to it, per language:

JavaScript (.js, .cjs, .mjs, .jsx) and Vue (.vue)

Section titled “JavaScript (.js, .cjs, .mjs, .jsx) and Vue (.vue)”
  • Control flow: if_statement, do_statement, for_statement, while_statement, for_in_statement, ternary_expression, switch_case, switch_default, catch_clause
  • Functions: function_declaration, generator_function_declaration, arrow_function, generator_function, method_definition, class_static_block, function_expression
  • Logical operators: &&, ||, ?? in binary expressions

TypeScript and TSX (.ts, .cts, .mts, .tsx)

Section titled “TypeScript and TSX (.ts, .cts, .mts, .tsx)”
  • Control flow: if_statement, do_statement, for_statement, while_statement, for_in_statement, ternary_expression, conditional_type, switch_case, switch_default, catch_clause
  • Functions: function_declaration, generator_function_declaration, arrow_function, generator_function, method_definition, class_static_block, function_expression
  • Logical operators: &&, ||, ?? in binary expressions
  • Control flow: if_statement, do_statement, for_statement, while_statement, enhanced_for_statement, ternary_expression, switch_label, catch_clause
  • Functions: constructor_declaration, method_declaration, lambda_expression, static_initializer, compact_constructor_declaration
  • Logical operators: &&, || in binary expressions
  • Control flow: if_expression, for_statement, while_statement, do_while_statement, elvis_expression, conjunction_expression, disjunction_expression, when_entry, catch_block
  • Functions: function_declaration, anonymous_function, anonymous_initializer, lambda_literal, secondary_constructor, setter, getter
  • Not counted: a guard on a when branch (is X if condition ->), which the grammar does not parse
  • Control flow: if_statement, do_statement, foreach_statement, for_statement, while_statement, conditional_expression, is_expression, and_pattern, or_pattern, switch_section, switch_expression_arm, catch_clause
  • Functions: constructor_declaration, method_declaration, lambda_expression, local_function_statement, accessor_declaration
  • Logical operators: &&, ||, ?? in binary expressions

C++ (.cpp, .cc, .cxx, .c++, .hh, .hpp, .hxx)

Section titled “C++ (.cpp, .cc, .cxx, .c++, .hh, .hpp, .hxx)”
  • Control flow: if_statement, do_statement, for_statement, while_statement, for_range_loop, conditional_expression, case_statement, catch_clause, seh_except_clause
  • Functions: lambda_expression, function_definition, abstract_function_declarator, function_declarator
  • Logical operators: &&, ||, and, or, xor in binary expressions
  • Control flow: if_statement, do_statement, for_statement, while_statement, conditional_expression, case_statement, seh_except_clause
  • Functions: function_definition, abstract_function_declarator, function_declarator
  • Logical operators: &&, || in binary expressions
  • Control flow: if_statement, do_statement, for_statement, while_statement, conditional_expression, case_statement, @catch
  • Functions: function_definition, block_expression
  • Logical operators: &&, || in binary expressions
  • Control flow: if_statement, elif_clause, if_clause, for_statement, while_statement, for_in_clause, conditional_expression, list, boolean_operator, case_pattern, except_clause
  • Functions: function_definition, lambda expressions (with specific nesting rules)
  • Control flow: if_statement, for_statement, communication_case, expression_case, type_case, default_case
  • Functions: method_declaration, func_literal, function_declaration, method_spec
  • Logical operators: &&, || in binary expressions
  • Control flow: if_statement, else_if_clause, do_statement, for_statement, while_statement, foreach_statement, conditional_expression, case_statement, default_statement, match_conditional_expression, match_default_expression, catch_clause
  • Functions: method_declaration, lambda_expression, arrow_function, anonymous_function, function_definition, function_static_declaration
  • Logical operators: &&, ||, ??, and, or, xor in binary expressions
  • Control flow: if, elsif, for, until, while, do_block, conditional, when, else, rescue
  • Functions: lambda, method, singleton_method
  • Logical operators: &&, ||, and, or in binary expressions
  • Control flow: if_statement, elif_clause, for_statement, while_statement, c_style_for_statement, ternary_expression, list, case_item
  • Functions: function_definition
  • Logical operators: &&, || in binary expressions
  • Control flow: if_statement, guard_statement, for_statement, while_statement, repeat_while_statement, switch_entry, catch_block, defer_statement, nil_coalescing_expression, ternary_expression, willset_clause, didset_clause
  • Functions: function_declaration, init_declaration, deinit_declaration, lambda_literal, subscript_declaration, computed_getter, computed_setter
  • Logical operators: conjunction_expression, disjunction_expression
  • Control flow: if, ifElse, for, foreach, while, case, caseCase, repeat, try, exceptionHandler
  • Functions: defProc (procedure/function implementation), lambda
  • Logical operators: kAnd, kOr, kXor in exprBinary

One grammar covers ANSI SQL, T-SQL, PL/SQL, PL/pgSQL and MySQL, so no dialect has to be chosen.

  • Control flow: if_statement, elsif_clause (ELSIF, ELSEIF; a T-SQL ELSE IF is an if_statement of its own), when_clause (each WHEN of a CASE expression or statement), iif_expression (T-SQL IIF), while_statement, loop_statement, for_statement (FOR … LOOP, FOREACH), repeat_statement, exit_statement with a condition (EXIT WHEN, CONTINUE WHEN), exception_handler (each WHEN after EXCEPTION), catch_block (BEGIN CATCH), handler_declaration (MySQL DECLARE … HANDLER)
  • Functions: routine_definition: procedures, functions and triggers with a body, nested PL/SQL routines and MySQL’s single-statement routines included
  • Logical operators: logical_operator (AND, OR; the AND of BETWEEN is none)
  • Control flow: if_expression, while_expression, for_expression, loop_expression, match_arm (the if/while nodes also cover if let/while let; the _ wildcard arm counts like any other arm)
  • Functions: function_item, function_signature_item (bodyless trait method signatures), closure_expression
  • Logical operators: &&, || in binary expressions
  • Control flow blocks: @if, @else if, @for, each @case of @switch, @defer (@else, @empty, @default and @placeholder are not counted, like a plain else)
  • Structural directives: *ngIf, *ngFor, *ngSwitchCase, and [ngIf], [ngForOf], [ngSwitchCase] on an <ng-template>
  • Logical operators: &&, ||, the ternary ? : and ?? in bindings, event handlers and interpolations, also in an interpolation inside a plain attribute value, such as class="icon {{ done ? 'check' : 'copy' }}"
  • ICU messages: each case of a plural or select message, but not its other case, like a @default
  • Functions: a template has none, so its complexity is its logic complexity
  • Inline templates: the template of a @Component in a .ts file is measured the same way, and its complexity adds to the complexity and logic complexity of the .ts file

These nodes count as comment lines, per language:

  • JavaScript/TypeScript/TSX/Vue: comment, html_comment
  • Java: block_comment, line_comment
  • Kotlin: line_comment, multiline_comment
  • C#: comment
  • C/C++: comment
  • Objective-C: comment
  • Python: comment and unassigned string literals (used as block comments)
  • Go: comment
  • PHP: comment
  • Ruby: comment
  • Swift: comment, multiline_comment
  • Bash: comment
  • Delphi: comment (covers // line, { } brace, and (* *) star comments)
  • Rust: line_comment, block_comment (cover //, ///, //! and /* */, /** */, /*! */)
  • Angular templates: comment (<!-- -->)
  • SQL: comment (covers -- line, /* */ block and MySQL’s # comments)

These definitions count as functions, per language:

JavaScript, TypeScript, TSX and Vue (.js, .cjs, .mjs, .jsx, .ts, .cts, .mts, .tsx, .vue)

Section titled “JavaScript, TypeScript, TSX and Vue (.js, .cjs, .mjs, .jsx, .ts, .cts, .mts, .tsx, .vue)”
  • Simple functions: function_declaration, generator_function_declaration, method_definition, function_expression
  • Arrow functions: Assigned to variables (detected via variable_declarator with arrow_function value)
  • Methods and constructors: method_declaration, constructor_declaration, compact_constructor_declaration
  • Lambda expressions: Assigned to variables (detected via variable_declarator with lambda_expression value)
  • Simple functions: secondary_constructor, setter, getter
  • Complex functions: function declarations with a function body, whatever their signature holds (receiver, return type, modifiers, type parameters, where clause)
  • Not counted: lambda literals and anonymous functions, wherever they stand; they contribute only to complexity
  • Methods and constructors: constructor_declaration, method_declaration, local_function_statement, accessor_declaration
  • Lambda expressions: Assigned to variables (detected via variable_declarator with lambda_expression value)

C++ (.cpp, .cc, .cxx, .c++, .hh, .hpp, .hxx)

Section titled “C++ (.cpp, .cc, .cxx, .c++, .hh, .hpp, .hxx)”
  • Functions: function_definition
  • Lambda expressions: Assigned to variables (detected via init_declarator with lambda_expression value)
  • Functions: function_definition
  • Functions: function_definition (C functions), method_definition (Objective-C methods)
  • Functions: function_definition
  • Lambda expressions: Assigned to variables (detected via assignment with lambda value)
  • Functions: method_declaration, func_literal, function_declaration, method_spec
  • Simple functions: method_declaration, function_definition, function_static_declaration
  • Anonymous functions: Assigned to variables (detected via assignment_expression with anonymous_function, arrow_function, or lambda_expression value)
  • Methods: method, singleton_method
  • Lambda expressions: Assigned to variables (detected via assignment with lambda value)
  • Functions: function_definition
  • Functions: function_declaration, init_declaration, deinit_declaration, computed_getter, computed_setter
  • Functions: defProc (procedure/function implementation in the implementation section). Forward declarations in the interface section (declProc) are not counted, and lambda contributes only to complexity.
  • Functions: routine_definition: a procedure, function or trigger with a body after AS, IS, DECLARE or BEGIN, also inside a PL/SQL package or type body, and a MySQL routine whose body is a single statement (after the parameters of a procedure, the RETURNS of a function or the FOR EACH ROW of a trigger). Forward declarations in a package specification and a PostgreSQL trigger that only names its function are not counted. A body without BEGIN (T-SQL) goes on up to GO, the next CREATE or the end of the file, unless a bare BEGIN … END stands in its middle: the body ends there. Neither the END line of a body nor, in PostgreSQL, the line with its closing $$ counts for the routine’s rloc, like a closing brace.
  • Functions: function_item (free functions, impl methods, trait default methods) and function_signature_item (bodyless trait method signatures). closure_expression contributes only to complexity and is not counted as a function.
  • Functions: none, a template declares no functions

LOC is the total number of lines in the file, including empty lines and comments, from the first line to the last. For a Vue file, it counts the lines of its script block.

RLOC counts only lines that contain actual code, excluding:

  • Empty lines (whitespace only)
  • Comment-only lines
  • Lines that are part of multi-line comments

This metric is calculated by counting all lines that are not identified as comment nodes by the Tree-sitter parser for each language.

These nodes count as parameters, per language:

  • JavaScript/Vue: identifier parameters
  • TypeScript/TSX: required_parameter
  • Java: formal_parameter
  • Kotlin: parameter
  • C#: parameter
  • C++: parameter_declaration
  • C: parameter_declaration
  • Objective-C: parameter_declaration (C functions), keyword_declarator (Objective-C method parameters)
  • Python: identifier parameters in parameters node
  • Go: parameter_declaration
  • PHP: simple_parameter
  • Ruby: identifier parameters
  • Swift: parameter
  • Bash: not counted, as Bash functions declare no parameters
  • Delphi: declArg
  • Rust: parameter (the self receiver is a distinct self_parameter node and is excluded)
  • Angular templates: not counted, as a template declares no functions
  • SQL: parameter, in parentheses or, in T-SQL, as a bare @name type list

Message Chains is a code smell that detects method call chains with 4 or more consecutive calls (e.g., obj.a().b().c().d()), which can indicate tight coupling and violations of the Law of Demeter. The metric counts only method/function calls, not property accesses.

JavaScript, TypeScript, TSX and Vue (.js, .cjs, .mjs, .jsx, .ts, .cts, .mts, .tsx, .vue)

Section titled “JavaScript, TypeScript, TSX and Vue (.js, .cjs, .mjs, .jsx, .ts, .cts, .mts, .tsx, .vue)”
  • Chain nodes: call_expression, member_expression
  • Call nodes: call_expression
  • Chain nodes: method_invocation, field_access
  • Call nodes: method_invocation
  • Chain nodes: call_expression, navigation_expression
  • Call nodes: call_expression
  • Chain nodes: invocation_expression, member_access_expression
  • Call nodes: invocation_expression

C and C++ (.c, .h, .cpp, .cc, .cxx, .c++, .hh, .hpp, .hxx)

Section titled “C and C++ (.c, .h, .cpp, .cc, .cxx, .c++, .hh, .hpp, .hxx)”
  • Chain nodes: call_expression, field_expression
  • Call nodes: call_expression
  • Chain nodes: call_expression, field_expression, message_expression
  • Call nodes: call_expression, message_expression
  • Chain nodes: call, attribute
  • Call nodes: call
  • Chain nodes: call_expression, selector_expression
  • Call nodes: call_expression
  • Chain nodes: member_call_expression, scoped_call_expression, member_access_expression
  • Call nodes: member_call_expression, scoped_call_expression
  • Chain nodes: call
  • Call nodes: call

Message chains are not applicable to Bash as it does not support method chaining.

  • Chain nodes: call_expression, navigation_expression
  • Call nodes: call_expression
  • Chain nodes: exprCall, exprDot
  • Call nodes: exprCall, exprDot (paren-less Obj.M1.M2.M3.M4 chains). When exprDot is wrapped in exprCall (e.g. Obj.M1().M2().M3().M4()), only exprCall counts, preventing double-counting of message-chain calls.

Message chains are not applicable to SQL as it does not support method chaining.

  • Chain nodes: call_expression, field_expression
  • Call nodes: call_expression

Message chains are not counted in templates.

The code smell metrics are derived from the other metrics:

Counts the number of functions in a file that have more than 10 real lines of code (RLOC). This is a language-independent metric that uses the per-function RLOC values.

Counts the number of functions in a file that have more than 4 parameters. This is a language-independent metric that uses the per-function parameter counts.

Binary metric (0 or 1) that indicates whether a file has more than 10 comment lines. This threshold helps identify files that may be over-commented.

Calculates the ratio of comment lines to real lines of code (comment_lines / rloc). The result is rounded to two decimal places. If RLOC is 0, the ratio is 0.0.

  • In Ruby the lambda keyword is not counted correctly for complexity and number of functions.
  • In C, C++ and Objective-C, void as a parameter counts as 1 for parameters per function.
Terminal window
ccsh unifiedparser src/test/resources/foo.ts -o foo.cc.json
ccsh unifiedparser src/test/resources -o foo.cc.json -nc --verbose
ccsh unifiedparser src/test/resources -o foo.cc.json --include-build-folders -e=something -e=/.*\.foo
ccsh unifiedparser src/test/resources -o foo.cc.json --bypass-gitignore

To add the code metrics to another map, pipe it in and pass - as an extra argument. The merged project keeps the project name of the Unified Parser:

Terminal window
cat pipeInput.cc.json | ccsh unifiedparser src/test/resources - -o merged.cc.json

A file the parser cannot analyse is left out of the map; see files a parser cannot analyse.