diff --git a/README.md b/README.md index cf76f0ac3..83ff2f617 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ trunk check enable {linter} | PowerShell | [psscriptanalyzer] | | Prisma | [prisma] | | Protobuf | [buf] (breaking, lint, and format), [clang-format], [clang-tidy] | -| Python | [autopep8], [bandit], [black], [flake8], [isort], [mypy], [nbstripout], [pylint], [pyright], [semgrep], [yapf], [ruff], [sourcery], [ty] | +| Python | [autopep8], [bandit], [black], [flake8], [isort], [mypy], [nbstripout], [pylint], [pyright], [basedpyright], [semgrep], [yapf], [ruff], [sourcery], [ty] | | Rego | [regal], [opa] | | Renovate | [renovate] | | Ruby | [brakeman], [rubocop], [rufo], [semgrep], [standardrb] | @@ -161,6 +161,7 @@ trunk check enable {linter} [prisma]: https://github.com/prisma/prisma#readme [psscriptanalyzer]: https://github.com/PowerShell/PSScriptAnalyzer [pylint]: https://github.com/PyCQA/pylint#readme +[basedpyright]: https://github.com/DetachHead/basedpyright [pyright]: https://github.com/microsoft/pyright [regal]: https://github.com/StyraInc/regal#readme [remark-lint]: https://github.com/remarkjs/remark-lint#readme diff --git a/linters/basedpyright/basedpyright.test.ts b/linters/basedpyright/basedpyright.test.ts new file mode 100644 index 000000000..67e00e8f9 --- /dev/null +++ b/linters/basedpyright/basedpyright.test.ts @@ -0,0 +1,3 @@ +import { linterCheckTest } from "tests"; + +linterCheckTest({ linterName: "basedpyright" }); diff --git a/linters/basedpyright/basedpyright_to_sarif.py b/linters/basedpyright/basedpyright_to_sarif.py new file mode 100755 index 000000000..b7d9aafa0 --- /dev/null +++ b/linters/basedpyright/basedpyright_to_sarif.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +import json +import sys + +results = [] + +for result in json.load(sys.stdin)["generalDiagnostics"]: + parse = { + "level": result["severity"] if result["severity"] != "information" else "note", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": result["file"], + }, + # Add region only if range information is available + # e.g. reportImportCycles does not have "range" information + **( + { + "region": { + "startLine": result["range"]["start"]["line"] + + 1, # basedpyright is 0-indexed, SARIF is 1-indexed + "startColumn": result["range"]["start"]["character"] + + 1, + "endLine": result["range"]["end"]["line"] + 1, + "endColumn": result["range"]["end"]["character"] + 1, + } + } + if "range" in result + else {} + ), + } + } + ], + "message": { + "text": result["message"].replace("Â", ""), + }, + } + if "rule" in result: + parse["ruleId"] = result["rule"] + results.append(parse) + +sarif = { + "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + "version": "2.1.0", + "runs": [{"results": results}], +} + +print(json.dumps(sarif, indent=2)) diff --git a/linters/basedpyright/plugin.yaml b/linters/basedpyright/plugin.yaml new file mode 100644 index 000000000..4c8923054 --- /dev/null +++ b/linters/basedpyright/plugin.yaml @@ -0,0 +1,38 @@ +version: 0.1 +tools: + definitions: + - name: basedpyright + runtime: python + package: basedpyright + shims: [basedpyright] + known_good_version: 1.28.1 +lint: + definitions: + - name: basedpyright + files: [python] + suggest_if: config_present + description: + Basedpyright is a fork of pyright with various type checking improvements, pylance features + and more + commands: + - name: lint + output: sarif + run: basedpyright --outputjson ${target} + success_codes: [0, 1] + read_output_from: stdout + batch: true + cache_results: false + parser: + runtime: python + run: python3 ${plugin}/linters/basedpyright/basedpyright_to_sarif.py + tools: [basedpyright] + direct_configs: + - pyrightconfig.json + affects_cache: + - pyproject.toml + - setup.cfg + issue_url_format: https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#{} + known_good_version: 1.28.1 + version_command: + parse_regex: basedpyright ${semver} + run: basedpyright --version diff --git a/linters/basedpyright/test_data/basedpyright_v1.28.1_basic.check.shot b/linters/basedpyright/test_data/basedpyright_v1.28.1_basic.check.shot new file mode 100644 index 000000000..5277e0904 --- /dev/null +++ b/linters/basedpyright/test_data/basedpyright_v1.28.1_basic.check.shot @@ -0,0 +1,607 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Testing linter basedpyright test basic 1`] = ` +{ + "issues": [ + { + "code": "reportUnusedImport", + "column": "20", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportUnusedImport", + "level": "LEVEL_MEDIUM", + "line": "1", + "linter": "basedpyright", + "message": "Import "Callable" is not accessed", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "8", + "offset": "19", + }, + ], + "targetType": "python", + }, + { + "code": "reportDeprecated", + "column": "30", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportDeprecated", + "level": "LEVEL_MEDIUM", + "line": "1", + "linter": "basedpyright", + "message": "This type is deprecated as of Python 3.9; use "collections.abc.Iterator" instead", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "8", + "offset": "29", + }, + ], + "targetType": "python", + }, + { + "code": "reportUnusedImport", + "column": "30", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportUnusedImport", + "level": "LEVEL_MEDIUM", + "line": "1", + "linter": "basedpyright", + "message": "Import "Iterator" is not accessed", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "8", + "offset": "29", + }, + ], + "targetType": "python", + }, + { + "code": "reportDeprecated", + "column": "40", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportDeprecated", + "level": "LEVEL_MEDIUM", + "line": "1", + "linter": "basedpyright", + "message": "This type is deprecated as of Python 3.10; use "|" instead", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "5", + "offset": "39", + }, + ], + "targetType": "python", + }, + { + "code": "reportUnusedImport", + "column": "40", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportUnusedImport", + "level": "LEVEL_MEDIUM", + "line": "1", + "linter": "basedpyright", + "message": "Import "Union" is not accessed", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "5", + "offset": "39", + }, + ], + "targetType": "python", + }, + { + "code": "reportDeprecated", + "column": "47", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportDeprecated", + "level": "LEVEL_MEDIUM", + "line": "1", + "linter": "basedpyright", + "message": "This type is deprecated as of Python 3.10; use "| None" instead", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "8", + "offset": "46", + }, + ], + "targetType": "python", + }, + { + "code": "reportUnusedImport", + "column": "47", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportUnusedImport", + "level": "LEVEL_MEDIUM", + "line": "1", + "linter": "basedpyright", + "message": "Import "Optional" is not accessed", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "8", + "offset": "46", + }, + ], + "targetType": "python", + }, + { + "code": "reportAttributeAccessIssue", + "column": "57", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportAttributeAccessIssue", + "level": "LEVEL_HIGH", + "line": "1", + "linter": "basedpyright", + "message": ""Enum" is unknown import symbol", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "4", + "offset": "56", + }, + ], + "targetType": "python", + }, + { + "code": "reportUnknownVariableType", + "column": "57", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportUnknownVariableType", + "level": "LEVEL_MEDIUM", + "line": "1", + "linter": "basedpyright", + "message": "Type of "Enum" is unknown", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "4", + "offset": "56", + }, + ], + "targetType": "python", + }, + { + "column": "13", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#", + "level": "LEVEL_LOW", + "line": "15", + "linter": "basedpyright", + "message": "Type of "a.x" is "int | str"", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "3", + "offset": "384", + }, + ], + "targetType": "python", + }, + { + "code": "reportAttributeAccessIssue", + "column": "3", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportAttributeAccessIssue", + "level": "LEVEL_HIGH", + "line": "18", + "linter": "basedpyright", + "message": "Cannot assign to attribute "x" for class "A" +  Expression of type "float" cannot be assigned to attribute "x" of class "A" +    Type "float" is not assignable to type "int | str" +      "float" is not assignable to "int" +      "float" is not assignable to "str"", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "1", + "offset": "462", + }, + ], + "targetType": "python", + }, + { + "code": "reportUnknownVariableType", + "column": "5", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportUnknownVariableType", + "level": "LEVEL_MEDIUM", + "line": "24", + "linter": "basedpyright", + "message": "Type of "y" is unknown", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "1", + "offset": "599", + }, + ], + "targetType": "python", + }, + { + "code": "reportUndefinedVariable", + "column": "8", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportUndefinedVariable", + "level": "LEVEL_HIGH", + "line": "24", + "linter": "basedpyright", + "message": ""ClassVar" is not defined", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "8", + "offset": "602", + }, + ], + "targetType": "python", + }, + { + "code": "reportUnannotatedClassAttribute", + "column": "14", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportUnannotatedClassAttribute", + "level": "LEVEL_MEDIUM", + "line": "27", + "linter": "basedpyright", + "message": "Type annotation for attribute \`z\` is required because this class is not decorated with \`@final\`", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "1", + "offset": "680", + }, + ], + "targetType": "python", + }, + { + "code": "reportUnknownArgumentType", + "column": "7", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportUnknownArgumentType", + "level": "LEVEL_MEDIUM", + "line": "30", + "linter": "basedpyright", + "message": "Argument type is unknown +  Argument corresponds to parameter "values" in function "print"", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "3", + "offset": "729", + }, + ], + "targetType": "python", + }, + { + "code": "reportUnknownMemberType", + "column": "7", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportUnknownMemberType", + "level": "LEVEL_MEDIUM", + "line": "30", + "linter": "basedpyright", + "message": "Type of "y" is unknown", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "3", + "offset": "729", + }, + ], + "targetType": "python", + }, + { + "code": "reportUnknownArgumentType", + "column": "7", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportUnknownArgumentType", + "level": "LEVEL_MEDIUM", + "line": "31", + "linter": "basedpyright", + "message": "Argument type is unknown +  Argument corresponds to parameter "values" in function "print"", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "3", + "offset": "740", + }, + ], + "targetType": "python", + }, + { + "code": "reportUnknownMemberType", + "column": "7", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportUnknownMemberType", + "level": "LEVEL_MEDIUM", + "line": "31", + "linter": "basedpyright", + "message": "Type of "z" is unknown", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "3", + "offset": "740", + }, + ], + "targetType": "python", + }, + { + "code": "reportAttributeAccessIssue", + "column": "9", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportAttributeAccessIssue", + "level": "LEVEL_HIGH", + "line": "31", + "linter": "basedpyright", + "message": "Cannot access attribute "z" for class "type[A]" +  Attribute "z" is unknown", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "1", + "offset": "742", + }, + ], + "targetType": "python", + }, + { + "code": "reportUntypedBaseClass", + "column": "13", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportUntypedBaseClass", + "level": "LEVEL_MEDIUM", + "line": "35", + "linter": "basedpyright", + "message": "Base class type is unknown, obscuring type of derived class", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "4", + "offset": "803", + }, + ], + "targetType": "python", + }, + { + "code": "reportUnannotatedClassAttribute", + "column": "5", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportUnannotatedClassAttribute", + "level": "LEVEL_MEDIUM", + "line": "36", + "linter": "basedpyright", + "message": "Type annotation for attribute \`RED\` is required because this class is not decorated with \`@final\`", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "3", + "offset": "814", + }, + ], + "targetType": "python", + }, + { + "code": "reportUnannotatedClassAttribute", + "column": "5", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportUnannotatedClassAttribute", + "level": "LEVEL_MEDIUM", + "line": "37", + "linter": "basedpyright", + "message": "Type annotation for attribute \`BLUE\` is required because this class is not decorated with \`@final\`", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "4", + "offset": "826", + }, + ], + "targetType": "python", + }, + { + "code": "reportReturnType", + "column": "29", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportReturnType", + "level": "LEVEL_HIGH", + "line": "39", + "linter": "basedpyright", + "message": "Function with declared return type "bool" must return value on all code paths +  "None" is not assignable to "bool"", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "4", + "offset": "864", + }, + ], + "targetType": "python", + }, + { + "code": "reportReturnType", + "column": "12", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportReturnType", + "level": "LEVEL_HIGH", + "line": "5", + "linter": "basedpyright", + "message": "Type "int" is not assignable to return type "str" +  "int" is not assignable to "str"", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "1", + "offset": "105", + }, + ], + "targetType": "python", + }, + { + "column": "25", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#", + "level": "LEVEL_LOW", + "line": "51", + "linter": "basedpyright", + "message": "Type of "val" is "int"", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "3", + "offset": "1128", + }, + ], + "targetType": "python", + }, + { + "column": "39", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#", + "level": "LEVEL_LOW", + "line": "54", + "linter": "basedpyright", + "message": "Type of "val" is "int"", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "3", + "offset": "1244", + }, + ], + "targetType": "python", + }, + { + "code": "reportUnusedCallResult", + "column": "9", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportUnusedCallResult", + "level": "LEVEL_MEDIUM", + "line": "57", + "linter": "basedpyright", + "message": "Result of call expression is of type "int" and is not used; assign to variable "_" if this is intentional", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "9", + "offset": "1280", + }, + ], + "targetType": "python", + }, + { + "code": "reportRedeclaration", + "column": "7", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportRedeclaration", + "level": "LEVEL_MEDIUM", + "line": "7", + "linter": "basedpyright", + "message": "Class declaration "A" is obscured by a declaration of the same name", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "1", + "offset": "183", + }, + ], + "targetType": "python", + }, + { + "code": "reportUnannotatedClassAttribute", + "column": "14", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportUnannotatedClassAttribute", + "level": "LEVEL_MEDIUM", + "line": "9", + "linter": "basedpyright", + "message": "Type annotation for attribute \`x\` is required because this class is not decorated with \`@final\`", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "1", + "offset": "230", + }, + ], + "targetType": "python", + }, + { + "code": "reportUninitializedInstanceVariable", + "column": "14", + "file": "test_data/basic.in.py", + "issueClass": "ISSUE_CLASS_EXISTING", + "issueUrl": "https://github.com/DetachHead/basedpyright/blob/main/docs/configuration/config-files.md#reportUninitializedInstanceVariable", + "level": "LEVEL_HIGH", + "line": "9", + "linter": "basedpyright", + "message": "Instance variable "x" is not initialized in the class body or __init__ method", + "ranges": [ + { + "filePath": "test_data/basic.in.py", + "length": "1", + "offset": "230", + }, + ], + "targetType": "python", + }, + ], + "lintActions": [ + { + "command": "lint", + "fileGroupName": "python", + "linter": "basedpyright", + "paths": [ + "test_data/basic.in.py", + ], + "verb": "TRUNK_VERB_CHECK", + }, + { + "command": "lint", + "fileGroupName": "python", + "linter": "basedpyright", + "paths": [ + "test_data/basic.in.py", + ], + "upstream": true, + "verb": "TRUNK_VERB_CHECK", + }, + ], + "taskFailures": [], + "unformattedFiles": [], +} +`; diff --git a/linters/basedpyright/test_data/basic.in.py b/linters/basedpyright/test_data/basic.in.py new file mode 100644 index 000000000..69879b580 --- /dev/null +++ b/linters/basedpyright/test_data/basic.in.py @@ -0,0 +1,57 @@ +from typing import Callable, Iterator, Union, Optional, Enum + + +def wrong_type(x: int) -> str: + return x # error: Incompatible return value type (got "int", expected "str") + +class A: + def method1(self) -> None: + self.x = 1 + + def method2(self) -> None: + self.x = "" # Mypy treats this as an error because `x` is implicitly declared as `int` + +a = A() +reveal_type(a.x) + +a.x = "" # Pyright allows this because the type of `x` is `int | str` +a.x = 3.0 # Pyright treats this as an error because the type of `x` is `int | str` + + + +class A: + x: int = 0 # Regular class variable + y: ClassVar[int] = 0 # Pure class variable + + def __init__(self): + self.z = 0 # Pure instance variable + +print(A.x) +print(A.y) +print(A.z) # pyright shows error, mypy shows no error + + + +class Color(Enum): + RED = 1 + BLUE = 2 + +def is_red(color: Color) -> bool: + if color == Color.RED: + return True + elif color == Color.BLUE: + return False + # mypy reports error: Missing return statement + + +def func(val: int | None): + if val is not None: + + def inner_1() -> None: + reveal_type(val) + print(val + 1) # mypy produces a false positive error here + + inner_2 = lambda: reveal_type(val) + 1 + + inner_1() + inner_2()