Skip to content

.qlint - A Linter for Q

Overview

QLint is a static q/kdb code analysis tool. The software checks a wide variety of standard q rules, and provides the capability for users to configure and add rules for their code.

Basic Usage

.qlint.lintFile "myFile.q"

Rules

The linter tests code for violations of one or more rules. The output of the linter is a table reporting broken rules, the code that violated the rule, the location in the code, and any additional error messages. QLint provides a number of default rules that cover a wide variety of potentially erroneous code. These rules are found in the table .qlint.rules.defaultRules and are summarized below.

Linter Output

The output from the linter is a table of broken rules. The columns of the table are described below.

Column Type Description
label symbol The label is the name of the rule
errorClass symbol The errorClass is the high level group that the error falls into and is one of either error, warning, or info
description string A high level description of the rule. This is set in the rules table used for linting
problemText string The text that was flagged as problematic
errorMessage string A more specific error message pertaining to the specific rule broken
startLine long The starting line where the problematic code was observed
startCol long The starting column where the problematic code was observed
endLine long The ending line where the problematic code was observed
endCol long The ending column where the problematic code was observed

The label is the name of the rule. It is a single word containing only uppercase letters and underscores. This name defines the rule in the rule table. The errorClass is the high-level category for the error. The three default categories are: error, warning, and info. The error category reports significant problems with the code that will very likely lead to runtime or compile errors. The warning category reports code that may lead to undesired outcomes, but is not necessarily fatal to the program. The info category indicates problems with the documentation of files. Users can create new rule categories by modifying errorClass column in the rule table provided to the linter. The errorMessage is a more specific description of the broken rule and can provide additional info about the context where the rule is broken.

Running QLint

There are many functions in the .qlint namespace that can be used to lint q code from a q process:

lintItem

.qlint.lintItem is used to lint code fragments. As such, rules involving the workspace, artifact type, or artifact name are ignored.

.qlint.lintItem["5?0Ng";::]

/=> label      errorClass description                                                                     problemText errorMessage startLine startCol endLine endCol
/=> ----------------------------------------------------------------------------------------------------------------------------------------------------------------
/=> FIXED_SEED warning    "Inputting a positive number into ?0Ng will result in the same seed every run." "5?0Ng"     ""           0         0        0       4

lint

.qlint.lint provides greater control over the linter's runtime parameters and it provides access to a greater variety of file type dependent rules than .qlint.lintItem. However, it cannot be used to run rules that require information about the workspace or artifact name.

The fourth argument to this function is fileType. It represents the type of the file being passed to the linter. Six file types are allowed: file, testfile, data, function, fragment, and module. The fileType affects what linter rules apply to the item and the parsing method.

  • Type file is parsed as a .q file.
  • Type testfile is parsed as a .quke test file.
  • Type module is parsed as a list of symbols used for handling dependencies.
  • The other file types are parsed as q code
.qlint.lint["$[a;a:1;a:2;b:3];a,b";enlist ".";::;`function;""]

/=> label                  errorClass description                                                                           problemText        errorMessage startLine startCol endLine endCol
/=> -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
/=> FUNCTION_START         warning    "Function artifact must start with a function"                                        "$[a;a:1;a:2;b:3]" ""           0         0        0       15
/=> MISSING_OVERVIEW       info       "Missing @fileOverview tag with associated description"                               ""                 ""           0         0        0       0
/=> MISSING_RETURNS        info       "Missing @returns tag"                                                                ""                 ""           0         0        0       0
/=> COND_EVENARGS          error      "Conditional $ should not be used with an even number of arguments"                   "$[a;a:1;a:2;b:3]" ""           0         0        0       15
/=> UNDECLARED_VAR         warning    "Undeclared variable in function will be treated as global"                           ,"a"               ""           0         2        0       2
/=> DECLARED_AFTER_USE     error      "The variable was declared after being used"                                          ,"a"               ""           0         4        0       4
/=> CONDITIONALLY_DECLARED warning    "This variable may be undefined at this point, as it was only declared conditionally" ,"b"               ""           0         19       0       19

lintNS

.qlint.LintNS will take a list of namespaces and lint all functions in them or any sub-namespaces. If it is given an empty list of namespaces, it will search all namespaces. Namespaces are identified as any dictionary with the null symbol as the first key, with a corresponding value of generic null ("(::)"). This may pick up things that are not considered namespaces.

This function will not lint for any of the info linter rules, since those only apply to documentation. It will return a table of messages similar to the table returned by lintItem.

ruleTable: select from .qlint.rules.defaultRules where label in `UNUSED_PARAM`FIXED_SEED;
.foo.bar : {[x;y] :x+1};
.foo.blort : {:10?0ng};
.qlint.lintNS[`.foo;ruleTable]

/=> qualifiedName objType label        errorClass description                                                                    problemText errorMessage startLine startCol endLine endCol
/=> ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
/=> .foo.bar      function     UNUSED_PARAM warning    "This param was declared then never used"                                      ,"y"        ""           0         4        0       4
/=> .foo.blort    function     FIXED_SEED   warning    "Inputting a positive number into ?0Ng will result in the same seed every run" "10?0ng"    ""           0         2        0       7

lintFile

.qlint.lintFile will read and lint a file with the .q or .quke extension. The function takes a symbol or string as an argument, and works on absolute and relative paths.

.qlint.lintFile "foo/bar.q"

lintFolder

.qlint.lintFolder will take a folder name and recursively lint all files with the .q or .quke extension. The function takes a symbol or string as an argument, and works on absolute and relative paths.

.qlint.lintFolder "foo"

File Level Rule Configuration

Within a file, it is possible to ignore specific rules for that file. The tag @qlintsuppress can be used in a comment to indicate which rules should be ignored. Global and local suppression are supported.

To globally suppress rules, use the @qlintsuppress tag followed by a list of rules:

// @qlintsuppress MISSING_RETURN UNUSED_PARAM

To locally suppress rules, use the @qlintsuppress tag followed by a list of rules including a bracketed number indicating the number of lines to suppress the rule. In the example below, the MISSING_RETURN rule will be ignored for 2 lines after the comment, while the UNUSED_PARAM rule is will be ignored for 5 lines after the comment.

// @qlintsuppress MISSING_RETURN(2) UNUSED_PARAM(5)

Both local and global suppressions can be used together in the same comment.

// @qlintsuppress MISSING_RETURN UNUSED_PARAM(2)

In addition to the @qlintsuppress tag, it is possible to force utilization of certain rules using the @qlintinclude tag even if they are not included in the rule table provided to the linter function. In the below example, the tag will force the linter to include the RANDOM_GUIDS rule when linting the given file even if it was removed from .qlint.rules.defaultRules prior to being supplied to the linter function.

// @qlintinclude RANDOM_GUIDS

Like the @qlintsuppress tag, any number of rules are allowed with the given tag. The only rules that can be forced to be included are those in the default rule table.

Examples

Examples runs of the linter are presented below.

Function Example

Consider the function .qlint.test1:

// @fileOverview function
// @param x {int[}
{[x]
    $[1;2;5;6]
    }

If .qlint.test1 were linted, the output would be:

Linting 1 of 1 artifact failed:

error : 1
   COND_EVENARGS
      Description : Conditional $ should not be used with an even number of arguments
      .qlint.test1
         (4:4) Problem Text: "$[1;2;5;6]"
warning : 1
   UNUSED_PARAM
      Description : This param was declared then never used
      .qlint.test1
         (3:2) Problem Text: "x"
info : 2
   MISSING_RETURNS
      Description : Missing @returns tag
      .qlint.test1
         (1:0) Problem Text: ""
   QDOC_TYPE
      Description : Invalid type in tag
      .qlint.test1
         (2:12) Problem Text: "int["
                Error Message: "Expected end of input, but found "[""

Clicking on the line with the problem text will open the failing file at the location where the problem text was found.

Suppression Example

If .qlint.test1 is modified to include a @qlintsuppress tag, this could be the file:

// @fileOverview function
// @qlintsuppress QDOC_TYPE MISSING_RETURNS
// @param x {int[}
{[x]
    $[1;2;5;6]; //@qlintsuppress COND_EVENARGS(0)
    $[3;4;7;8];
    }

If .qlint.test1 were linted, the output would be:

Linting 1 of 1 artifact failed:

error : 1
   COND_EVENARGS
      Description : Conditional $ should not be used with an even number of arguments
      .qlint.test1
         (6:4) Problem Text: "$[3;4;7;8]"
warning : 1
   UNUSED_PARAM
      Description : This param was declared then never used
      .qlint.test1
         (4:2) Problem Text: "x"

In this example, the QDOC_TYPE and MISSING_RETURN info rules have been ignored due to a global suppression. Furthermore, there is a local suppression of the DOLLARIF_EVENARGS rule. The incorrect conditional on line 5 is ignored, but the one on line 6 is not, since this rule was only suppressed locally.

Q File Example

Consider the following file, .qlint/any.q:

abc:1 2 3
 abc+1
def: 1 2 3
def+1

select a,'b from t
 where 1b, 0b

The output of linting this file would be:

Linting 1 of 1 artifact failed:

error : 1
   DECLARED_AFTER_USE
      Description : The variable was declared after being used
      .qlint/any.q
         (1:0) Problem Text: "abc"
warning : 3
   UNDECLARED_VAR
      Description : Undeclared variable in function will be treated as global
      .qlint/any.q
         (2:1)  Problem Text: "abc"
         (6:17) Problem Text: "t"
   UNPARENTHESIZED_JOIN
      Description : A potential join in this QSQL statement will be interpreted as separate statements unless wrapped in parentheses
      .qlint/any.q
         (6:0) Problem Text: "select a,'b from t
                               where 1b, 0b"

Note that this file does not violate the RESERVED_NAME rule even though the name of the file is "any". This is because this is a file, not a function or data artifact. Note also that only the "abc" variable is identified as being declared after use. This is because in .q files, when loaded with \l, any non-whitespace character at the start of a line is treated as a statement delimiter.

Quke File Example

Consider the following quke file, .qlint/test.quke:

feature
        expect
            1b
    bench
        behaviour
            til 1
        timelimit
            notatimelimit

The output of linting this file would be:

Linting 1 of 1 artifact failed:

error : 4
    INVALID_QUKE
       Description : A quke file was improperly formatted
       .qlint/test.quke
          (1:0)  Problem Text: "feature"
                 Error Message: "incorrect token in feature block"
          (2:8)  Problem Text: "expect"
                 Error Message: "expect statement in feature block is in improper location"
          (3:12) Problem Text: "1b"
                 Error Message: "function statement in feature block is in improper location"
          (7:8)  Problem Text: "timelimit"
                 Error Message: "only single number allowed in timelimit"

Rule Summary

Label ErrorClass Description
ASSIGN_RESERVED_WORD error Assignment to a reserved word
COND_EVENARGS error Conditional $ should not be used with an even number of arguments
DECLARED_AFTER_USE error The variable was declared after being used
GLOBAL_PEACH error Modifying globals inside a peach statement is not allowed
INVALID_ADVERB error A binary adverb cannot be applied to a unary function
INVALID_ASSIGN error Attempt to assign to a string, symbol, or number
INVALID_ESCAPE error Invalid Escape Sequence: Valid escape sequences are: \n,\r,\t,/,\\,\/ and three digit octal sequences \377 or smaller
INVALID_QUKE error A quke file was improperly formatted
OVERWRITE_ARTIFACT error Variable assignment overwrites namespace or artifact
STATEMENT_IN_EXPR error If, while, or do statement used in expression, possible missing semicolon
RESERVED_NAME error File has reserved name
TOO_MANY_CONSTANTS error Too many constants in a function
TOO_MANY_GLOBALS error Too many globals in a function
TOO_MANY_LOCALS error Too many locals in a function
UNINDENTED_CODE error Any multiline expression must be indented after the first line
BACKWARD_COMPATIBILITY warning This function has backward compatibility issues with kdb versions less than 3.6
CAST_TYPE_NUMERICAL warning Casting using a short to indicate cast type is unnecessarily unclear. Another form is advised
CONDITIONALLY_DECLARED warning This variable may be undefined at this point, as it was only declared conditionally
DEBUG_FUNCTION warning Calling a debug function. Likely should not be in release version
DEPRECATED_DATETIME warning Datetime has been deprecated
DEPRECATED_FUNCTION warning This file uses a deprecated function
EMPTY_IF warning If statement lacks code to execute
FIXED_SEED warning Inputting a positive number into ?0Ng will result in the same seed every run
FUNCTION_START warning Function artifact must start with a function
INSUFFICIENT_INDENT warning Every line in the function other than the first must have an indentation at or deeper than the second line of the function, which must have an indentation of at least 1 extra space
INTERNAL warning Reference to an internal api of another module
INVALID_FUNCTION warning Function artifacts must be lambda definitions, rather than projections, immediately invoked functions, or functions in expressions
MALFORMED_SUPPRESSION warning Malformed @qlintsuppress tag
MISSING_DEPENDENCY warning Any reference to another namespace should be listed in the dependency list
NAME_COLLISION warning Executing statement in editor could overwrite global variable
NEED_EXPLICIT_RETURN warning Explicit return needed. Otherwise will return generic null
POSSIBLE_RETURN warning Assignment statement looks like return
UNDECLARED_VAR warning Undeclared variable in function will be treated as global
UNUSED_INTERNAL warning This function is marked as internal (is part of a sub-namespace i) but was never used within the namespace
UNUSED_PARAM warning This param was declared then never used
UNUSED_VAR warning This variable was declared then never used
RANDOM_GUIDS warning Multiple calls to ?0ng in quick succession, with negative numbers, can produce the same output
UNREACHABLE_CODE warning A preceding return prevents this statement from being reached
UNEXPECTED_COND_NEWLINE warning Condition should begin on same line as loop or if statement
UNPARENTHESIZED_JOIN warning A potential join in this QSQL statement will be interpreted as separate statements unless wrapped in parentheses
VAR_Q_ERROR warning Variable name the same as q error message. This can cause ambiguous error messages
DEFAULT_QDOC info The file has the default documentation
DUPLICATE_DEPENDENCY info Duplicate dependencies
INVALID_DEPENDENCY info Invalid dependencies
INVALID_TYPEDEF info Invalid typedef tag
INVALID_TAG info Tag not recognized as valid QDoc tag
MISSING_OVERVIEW info Missing @fileOverview tag with associated description
MISSING_RETURNS info Missing @returns tag
MISSING_TYPE info Missing type in returns or param tag
MULTIPLE_RETURNS info Multiple @returns tags
OUT_OF_ORDER_PARAM info Parameters out of order
PARAM_NOT_IN_CODE info This param is not in the function
QDOC_TYPE info Invalid type in tag
REDUNDANT_GLOBAL_ASSIGN info Using the global amend operator on a fully qualified name is redundant
UNDOCUMENTED_PARAM info Undocumented parameter
UNUSED_DEPENDENCY info Unused dependencies

.qlint.lint

Lints an entire segment of q code and reports any issues.

This function provides access to a greater variety of rules than .qlint.lintItem such as those that only run on files. It cannot be used to run rules relating to the workspace or requiring the artifact name.

Parameter(s):

Name Type Description
code string The code to be linted.
context string The context in which the variable is defined.
ruleTable table ([] label: symbol; nodeNames: symbol[]; func: fn; errorClass: symbol; description: string) | null A table of rules. Generic null (::) uses the default rule table.
fileType symbol The type of file to be run on. Can be any of `file`testfile`data`function`fragment. This is not necessarily the same as objType in a workspace.
dependencyList string A list of symbols indicating the contexts that are referenced.

Returns:

Name Type Description
<returns> table A table of linter results with one row for each message.
<returns>.label symbol
<returns>.errorClass symbol
<returns>.description string
<returns>.problemText string
<returns>.errorMessage string
<returns>.startLine long
<returns>.endLine long
<returns>.startIndex long
<returns>.endIndex long

Example: Lint a function artifact


 .qlint.lint["$[a;a:1;a:2;b:3];a,b";enlist ".";::;`function;""]

 /=> label                  errorClass description                                                                           problemText        errorMessage startLine startCol endLine endCol
 /=> -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 /=> FUNCTION_START         warning    "Function artifact must start with a function"                                        "$[a;a:1;a:2;b:3]" ""           0         0        0       15    
 /=> MISSING_OVERVIEW       info       "Missing @fileOverview tag with associated description"                               ""                 `            0         0        0       0     
 /=> MISSING_RETURNS        info       "Missing @returns tag"                                                                ""                 ""           0         0        0       0     
 /=> COND_EVENARGS          error      "Conditional $ should not be used with an even number of arguments"                   "$[a;a:1;a:2;b:3]" ""           0         0        0       15    
 /=> UNDECLARED_VAR         warning    "Undeclared variable in function will be treated as global"                           ,"a"               ""           0         2        0       2     
 /=> DECLARED_AFTER_USE     error      "The variable was declared after being used"                                          ,"a"               ""           0         4        0       4     
 /=> CONDITIONALLY_DECLARED warning    "This variable may be undefined at this point, as it was only declared conditionally" ,"b"               ""           0         19       0       19   

.qlint.lintFile

Lints a .q or .quke file and reports any issues.

Parameter(s):

Name Type Description
path string | symbol The file path

Returns:

Name Type Description
<returns> table A table of linter results with one row for each message.
<returns>.fileName symbol
<returns>.namespace symbol
<returns>.label symbol
<returns>.errorClass symbol
<returns>.description string
<returns>.problemText string
<returns>.errorMessage string
<returns>.startLine long
<returns>.endLine long
<returns>.startIndex long
<returns>.endIndex long

Example: Lint a file

 .qlint.lintFile `file.q

.qlint.lintFolder

Lints all .q or .quke files in a folder and reports any issues.

Parameter(s):

Name Type Description
path string | symbol | null The folder path or null. Null is replaced with "." namespace

Returns:

Name Type Description
<returns> table A table of linter results with one row for each message.
<returns>.fileName symbol
<returns>.namespace symbol
<returns>.label symbol
<returns>.errorClass symbol
<returns>.description string
<returns>.problemText string
<returns>.errorMessage string
<returns>.startLine long
<returns>.endLine long
<returns>.startIndex long
<returns>.endIndex long

Example: Lint a folder

 .qlint.lintFolder `folder

.qlint.lintItem

Lints a sample of q code and reports any issues.

Since this function is linting on a code fragment, rules involving variable tracking, file context references, and namespace dependency are ignored. It will also not test rules that require information on the workspace or artifact name. The fileType is `fragment.

Parameter(s):

Name Type Description
code string Code to be linted.
ruleTable table ([] label: symbol; nodeNames: symbol[]; func: fn; errorClass: symbol; description: string) | null A table of rules. Generic null (::) uses the default rule table.

Returns:

Name Type Description
<returns> table A table of linter results with one row for each message.
<returns>.label symbol
<returns>.errorClass symbol
<returns>.description string
<returns>.problemText string
<returns>.errorMessage string
<returns>.startLine long
<returns>.endLine long
<returns>.startIndex long
<returns>.endIndex long

Example: Lint a q string


 .qlint.lintItem["5?0Ng";::]

 /=> label      errorClass description                                                                     problemText errorMessage startLine startCol endLine endCol
 /=> ----------------------------------------------------------------------------------------------------------------------------------------------------------------
 /=> FIXED_SEED warning    "Inputting a positive number into ?0Ng will result in the same seed every run." "5?0Ng"     ""           0         0        0       4     

.qlint.lintNS

Lints one or more namespaces.

Namespaces are determined as any dictionary containing the key-value pair (`; ::). This may pick up things that are not considered namespaces. If namespaces is null, it will search all namespaces.

If an empty list is passed as a rule table, then the default rules will be used. Any rules checking for dependency will be ignored. Wrong dependencies are IDE-specific and cannot be determined by examining the namespace. In addition, it will not lint for any of the info linter rules, since those only apply to documentation. It returns a table of messages similar to the table returned by lintItem.

Parameter(s):

Name Type Description
namespaces symbol[] | symbol | null The names of the namespaces you want to lint. If you enter in null it will lint all namespaces excluding single letter namespaces.
ruleTable table ([] label: symbol; nodeNames: symbol[]; func: fn; errorClass: symbol; description: string) | null A table of rules or (::) to indicate a default list should be used

Returns:

Name Type Description
<returns> table A table of linter results with one row for each message.
<returns>.label symbol
<returns>.errorClass symbol
<returns>.description string
<returns>.problemText string
<returns>.errorMessage string
<returns>.startLine long
<returns>.endLine long
<returns>.startIndex long
<returns>.endIndex long

Example:


 ruleTable: select from .qlint.rules.defaultRules where label in `UNUSED_PARAM`FIXED_SEED;
 .foo.bar : {[x;y] :x+1};
 .foo.blort : {:10?0ng};
 .qlint.lintNS[`.foo;ruleTable]

 /=> qualifiedName objType label        errorClass description                                                                    problemText errorMessage startLine startCol endLine endCol
 /=> ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 /=> .foo.bar      function     UNUSED_PARAM warning    "This param was declared then never used"                                      ,"y"        ""           0         4        0       4
 /=> .foo.blort    function     FIXED_SEED   warning    "Inputting a positive number into ?0Ng will result in the same seed every run" "10?0ng"    ""           0         2        0       7

.qlint.lintWorkspace

Lint all artifacts in a workspace

Parameter(s):

Name Type Description
wsTbl table The artifacts to be linted
wsTbl.ref symbol
wsTbl.module symbol
wsTbl.name symbol
wsTbl.repoName symbol
wsTbl.artType symbol
wsTbl.content string

Returns:

Name Type Description
<returns> table A table of linter results with one row for each message.
<returns>.label symbol
<returns>.errorClass symbol
<returns>.description string
<returns>.problemText string
<returns>.errorMessage string
<returns>.startLine long
<returns>.endLine long
<returns>.startIndex long
<returns>.endIndex long

.qlint.linterSchema

The schema for the linter results