Skip to content

[Linting Rule] Unclosed Connection

github-actions[bot] edited this page Aug 20, 2026 · 1 revision

Generated from 'wiki-linter.ts' on 2026-08-18, 16:29:46 UTC (v2.14.0), please do not edit directly.

Unclosed Connection [overview]

smell robustness

This rule is a best-effort rule.

Flags connections that are opened but not closed on every path opening them.
This linting rule is implemented in src/linter/rules/unclosed-connection.ts.

Configuration

Linting rules can be configured by passing a configuration object to the linter query as shown in the example below. The unclosed-connection rule accepts the following configuration options:

  • closeFns
    functions closing the connection they are handed, besides the ones flowR states CallProp.Closes for
  • openFns
    functions opening a connection, besides the ones flowR states CallProp.Opens for

Examples

con <- file("data.csv")
readLines(con)

The linting query can be used to run this rule on the above example:

[ { "type": "linter",   "rules": [ { "name": "unclosed-connection",     "config": {} } ] } ]

Results (prettified and summarized):

Query: linter (3 ms)
   ╰ Unclosed Connection (unclosed-connection):
       ╰ certain:
           ╰ Unclosed connection at 1.8-23 (1 quick fix(es) available)
       ╰ Metadata: totalOpened: 1, totalClosed: 0, searchTimeMs: 2, processTimeMs: 1
All queries together required ≈3 ms (1ms accuracy, total 4 ms)

Show Detailed Results as Json

The analysis required 4.4 ms (including parsing and normalization and the query) within the generation environment.

In general, the JSON contains the Ids of the nodes in question as they are present in the normalized AST or the dataflow graph of flowR. Please consult the Interface wiki page for more information on how to get those.

{
  "linter": {
    "results": {
      "unclosed-connection": {
        "results": [
          {
            "certainty": "certain",
            "involvedId": 4,
            "loc": [
              1,
              8,
              1,
              23
            ],
            "quickFix": [
              {
                "type": "replace",
                "loc": [
                  2,
                  15,
                  2,
                  14
                ],
                "description": "Close the connection with `close(con)`",
                "replacement": "\nclose(con)"
              }
            ]
          }
        ],
        ".meta": {
          "totalOpened": 1,
          "totalClosed": 0,
          "searchTimeMs": 2,
          "processTimeMs": 1
        }
      }
    },
    ".meta": {
      "timing": 3
    }
  },
  ".meta": {
    "timing": 3
  }
}

Additional Examples

These examples are synthesized from the test cases in: test/functionality/linter/lint-unclosed-connection.test.ts

Test Case: All closed

Given the following input:

`a <- textConnection(A)
readLines(a, 2)
file <- file()
b <- textConnection(B)

close(a)
close(b)
close(file)`

We expect the linter to report the following:

* no lints

See here for the test-case implementation.

Test Case: Closed inline

Given the following input:

close(file("x"))

We expect the linter to report the following:

* no lints

See here for the test-case implementation.

Test Case: Never closed

Given the following input:

a <- file("x")

We expect the linter to report the following:

				certainty: LintingResultCertainty.Certain,
loc:       [1, 6, 1, 14],
quickFix:  [{
	type:        'replace',
	loc:         [1, 15, 1, 14],
	description: 'Close the connection with `close(a)`',
	replacement: '\nclose(a)'
}]

See here for the test-case implementation.

Test Case: Closed after the last use

Given the following input:

`read <- function(){
	con <- file("x")
	readLines(con)
}`

We expect the linter to report the following:

			certainty: LintingResultCertainty.Certain,
loc:       [2, 9, 2, 17],
quickFix:  [{
	type:        'replace',
	loc:         [3, 16, 3, 15],
	description: 'Close the connection with `close(con)`',
	replacement: '\n close(con)'
}]

See here for the test-case implementation.

Test Case: Only one closed

Given the following input:

`a <- textConnection(AB)
b <- a
if(x){
	b <- textConnection(LETTERS)
	close(b)
	close(b)
}
t <- 2`

We expect the linter to report the following:

			certainty: LintingResultCertainty.Certain,
loc:       [1, 6, 1, 23],
quickFix:  [{
	type:        'replace',
	loc:         [2, 7, 2, 6],
	description: 'Close the connection with `close(a)`',
	replacement: '\nclose(a)'
}]

See here for the test-case implementation.

Test Case: Closed with new definer

Given the following input:

`a <- textConnection(AB)
b <- a
c <- b
close(c)`

We expect the linter to report the following:

* no lints

See here for the test-case implementation.

Test Case: Closed by a wrapper function

Given the following input:

`shut <- function(con) close(con)
a <- textConnection(AB)
shut(a)`

We expect the linter to report the following:

* no lints

See here for the test-case implementation.

Test Case: Opened by a wrapper function

Given the following input:

`make <- function() textConnection(AB)
a <- make()
close(a)`

We expect the linter to report the following:

* no lints

See here for the test-case implementation.

Test Case: Closed in both branches

Given the following input:

`a <- textConnection(AB)
if(x){
	close(a)
} else {
	close(a)
}`

We expect the linter to report the following:

* no lints

See here for the test-case implementation.

Test Case: Closed on exit

Given the following input:

`read <- function(){
	con <- file("x")
	on.exit(close(con))
	readLines(con)
}
read()`

We expect the linter to report the following:

* no lints

See here for the test-case implementation.

Test Case: Closed by withr

Given the following input:

`con <- withr::local_connection(file("x"))
readLines(con)`

We expect the linter to report the following:

* no lints

See here for the test-case implementation.

Test Case: Database connection closed

Given the following input:

`con <- DBI::dbConnect(drv)
DBI::dbDisconnect(con)`

We expect the linter to report the following:

* no lints

See here for the test-case implementation.

Test Case: Database connection left open

Given the following input:

con <- DBI::dbConnect(drv)

We expect the linter to report the following:

				certainty: LintingResultCertainty.Certain,
loc:       [1, 8, 1, 26],
quickFix:  [{
	type:        'replace',
	loc:         [1, 27, 1, 26],
	description: 'Close the connection with `close(con)`',
	replacement: '\nclose(con)'
}]

See here for the test-case implementation.

Test Case: Configured functions

Given the following input:

`a <- myOpen("x")
b <- myOpen("y")
myClose(a)`

And using the following configuration:

{ openFns: ['myOpen'], closeFns: ['myClose'] }

We expect the linter to report the following:

			certainty: LintingResultCertainty.Certain,
loc:       [2, 6, 2, 16],
quickFix:  [{
	type:        'replace',
	loc:         [2, 17, 2, 16],
	description: 'Close the connection with `close(b)`',
	replacement: '\nclose(b)'
}]

See here for the test-case implementation.

Test Case: Not necessarily closed

Given the following input:

`a <- textConnection(AB)
b <- textConnection(E)
if(x){
	close(a)
}
t <- 2
close(b)`

We expect the linter to report the following:

			certainty: LintingResultCertainty.Uncertain,
loc:       [1, 6, 1, 23]

See here for the test-case implementation.

Test Case: Opened conditionally, closed unconditionally

Given the following input:

`if(x){
	a <- textConnection(A)
}
close(a)`

We expect the linter to report the following:

* no lints

See here for the test-case implementation.

Test Case: Openend and closed in different branches

Given the following input:

`a <- 4+3
if(x){
	a <- textConnection(A)
	b <- textConnection(B)
}
t <- 34
if(x){
	close(a)
}
if(y){
	close(b)
}`

We expect the linter to report the following:

			certainty: LintingResultCertainty.Uncertain,
loc:       [3, 7, 3, 23]
		},
		{
certainty: LintingResultCertainty.Uncertain,
loc:       [4, 7, 4, 23]

See here for the test-case implementation.

Test Case: Nested branches - not necessarily closed

Given the following input:

`a <- 4+3
if(x){
	a <- textConnection(A)
	b <- textConnection(B)
	if(y){
	close(a)
	}
}`

We expect the linter to report the following:

			certainty: LintingResultCertainty.Uncertain,
loc:       [3, 7, 3, 23]
		},
		{
certainty: LintingResultCertainty.Certain,
loc:       [4, 7, 4, 23],
quickFix:  [{
	type:        'replace',
	loc:         [4, 24, 4, 23],
	description: 'Close the connection with `close(b)`',
	replacement: '\n close(b)'
}]

See here for the test-case implementation.

Test Case: Opened and closed within the loop

Given the following input:

`for(f in files){
	con <- file(f)
	readLines(con)
	close(con)
}`

We expect the linter to report the following:

* no lints

See here for the test-case implementation.

Test Case: Nested branches - not closed

Given the following input:

`if(x){
	a <- 4
	while(a > 0){
		b <- textConnection(A)
		readLines(b, 2)
		a <- a - 1
	}
	close(b)
} 
else {
	a <- textConnection(A)
	close(a)
}`

We expect the linter to report the following:

			certainty: LintingResultCertainty.Uncertain,
loc:       [4, 8, 4, 24]

See here for the test-case implementation.

Clone this wiki locally