Skip to content

Commit f53b87f

Browse files
authored
CLS/chplcheck: Support editor agnostic configuration files (#26978)
This PR expands the existing support for using ConfigArgParse to allow for editor-agnostic configuration files. This allows users to specify options for their project in an editor-agnostic way, and then allow CLS/chplcheck to auto-pick them up. This also integrates nicely with Mason, allowing users to specify CLS/chplcheck options in a Mason.toml file. For example ```toml [brick] ... [tool.chplcheck] disable-rule = ["UnusedLoopIndex"] ``` This PR also expands the options for chplcheck, allowing the following ```toml ... [tool.chplcheck] file = ["src/*.chpl"] add-rules = ["lint/rules.py"] disable-rule = ["IncorrectIndentation"] ``` This lets users specify all the options needed in 1 file, and then from the project root running just `chplcheck` does the right thing This PR also adds the -M/--module-dir options to CLS, allowing users to add a few paths to the module search path instead of needing a full .cls-commands file. This also includes adding an internal, developer only, ability to set the root directory for standard modules. - [x] paratest with/without gasnet [Reviewed by @DanilaFe]
2 parents 8b80d1a + 73b7b08 commit f53b87f

File tree

18 files changed

+192
-17
lines changed

18 files changed

+192
-17
lines changed

compiler/main/driver.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -2346,6 +2346,7 @@ static void dynoConfigureContext(std::string chpl_module_path) {
23462346

23472347
chpl::parsing::setupModuleSearchPaths(gContext,
23482348
CHPL_HOME,
2349+
"", //moduleRoot
23492350
fMinimalModules,
23502351
CHPL_LOCALE_MODEL,
23512352
fEnableTaskTracking,

doc/rst/tools/chpl-language-server/chpl-language-server.rst

+29
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,35 @@ language server with linting enabled various ``chplcheck`` flags:
141141
--chplcheck-disable-rule UnusedLoopIndex \
142142
--chplcheck-add-rules path/to/my/myrules.py
143143
144+
Configuration Files
145+
^^^^^^^^^^^^^^^^^^^
146+
147+
``chpl-language-server`` can be configured without passing command line flags using a
148+
configuration file. This file can be specified using the ``--config`` (also
149+
``-c``) flag. The configuration file can either be a YAML file or a specific
150+
TOML file. For example, running ``chpl-language-server -c config.yaml`` will load the
151+
configuration from ``config.yaml``. Additionally, ``chpl-language-server`` will look for a
152+
configuration files in the current directory named ``chpl-language-server.cfg`` or
153+
``.chpl-language-server.cfg`` (in that order).
154+
155+
Most command line options can be specified in the configuration file. For
156+
example, the following YAML configuration file will enable end of block markers for loops and declarations.
157+
158+
.. code-block:: yaml
159+
160+
end-markers: "loop,decl"
161+
162+
TOML configuration files can also be used, for example the following is the same configuration as above:
163+
164+
.. code-block:: toml
165+
166+
[tool.chpl-language-server]
167+
end-markers = ["loop", "decl"]
168+
169+
This configuration can also be added to a :ref:`Mason <readme-mason>`
170+
configuration file. ``CLS`` will automatically use a configuration file
171+
contained in a ``Mason.toml`` file in the current directory.
172+
144173
Configuring Chapel Projects
145174
---------------------------
146175

doc/rst/tools/chplcheck/chplcheck.rst

+33
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,39 @@ as at least 1 rule opts in to using them. For example, custom rules could confor
148148
to the setting ``MyIgnoreList``, which could be set as
149149
``--setting MyIgnoreList="foo,bar"``.
150150

151+
Configuration Files
152+
-------------------
153+
154+
``chplcheck`` can be configured without passing command line flags using a
155+
configuration file. This file can be specified using the ``--config`` (also
156+
``-c``) flag. The configuration file can either be a YAML file or a specific
157+
TOML file. For example, running ``chplcheck -c config.yaml`` will load the
158+
configuration from ``config.yaml``. Additionally, ``chplcheck`` will look for a
159+
configuration files in the current directory named ``chplcheck.cfg`` or
160+
``.chplcheck.cfg`` (in that order).
161+
162+
Most command line options can be specified in the configuration file. For
163+
example, the following YAML configuration file will explicitly enable the
164+
``UseExplicitModules`` rule and disable the ``UnusedLoopIndex`` and
165+
``UnusedFormal`` rules:
166+
167+
.. code-block:: yaml
168+
169+
enable-rule: ["UseExplicitModules"]
170+
disable-rule: ["UnusedLoopIndex", "UnusedFormal"]
171+
172+
TOML configuration files can also be used, for example the following is the same configuration as above:
173+
174+
.. code-block:: toml
175+
176+
[tool.chplcheck]
177+
enable-rule = ["UseExplicitModules"]
178+
disable-rule = ["UnusedLoopIndex", "UnusedFormal"]
179+
180+
This configuration can also be added to a :ref:`Mason <readme-mason>`
181+
configuration file. ``chplcheck`` will automatically use a configuration file
182+
contained in a ``Mason.toml`` file in the current directory.
183+
151184
Setting Up In Your Editor
152185
-------------------------
153186

frontend/include/chpl/parsing/parsing-queries.h

+18
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,11 @@ void setBundledModulePath(Context* context, UniqueString path);
299299
chplSysModulesSubdir -- CHPL_SYS_MODULES_SUBDIR
300300
chplModulePath -- CHPL_MODULE_PATH
301301
302+
The 'moduleRoot' argument allows overriding the default module root. If
303+
'moduleRoot' is the empty string, then the default of 'CHPL_HOME/modules' is
304+
used. Regardless, if 'minimalModules' is true '/minimal' is appended to the
305+
'moduleRoot'.
306+
302307
The arguments 'prependInternalModulePaths' and 'prependStandardModulePaths',
303308
if non-empty, allow one to override where the context will search for
304309
internal and standard modules, respectively. It will search each successive
@@ -312,6 +317,7 @@ void setBundledModulePath(Context* context, UniqueString path);
312317
void setupModuleSearchPaths(
313318
Context* context,
314319
const std::string& chplHome,
320+
const std::string& moduleRoot,
315321
bool minimalModules,
316322
const std::string& chplLocaleModel,
317323
bool enableTaskTracking,
@@ -324,6 +330,18 @@ void setupModuleSearchPaths(
324330
const std::vector<std::string>& cmdLinePaths,
325331
const std::vector<std::string>& inputFilenames);
326332

333+
/**
334+
Overload of the more general setupModuleSearchPaths that uses the
335+
context's stored chplHome and chplEnv to determine the values of most
336+
arguments.
337+
*/
338+
void setupModuleSearchPaths(Context* context,
339+
const std::string& moduleRoot,
340+
bool minimalModules,
341+
bool enableTaskTracking,
342+
const std::vector<std::string>& cmdLinePaths,
343+
const std::vector<std::string>& inputFilenames);
344+
327345
/**
328346
Overload of the more general setupModuleSearchPaths that uses the
329347
context's stored chplHome and chplEnv to determine the values of most

frontend/lib/parsing/parsing-queries.cpp

+17-2
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,7 @@ addCommandLineFileDirectories(std::vector<std::string>& searchPath,
652652
void setupModuleSearchPaths(
653653
Context* context,
654654
const std::string& chplHome,
655+
const std::string& moduleRoot,
655656
bool minimalModules,
656657
const std::string& chplLocaleModel,
657658
bool enableTaskTracking,
@@ -668,10 +669,13 @@ void setupModuleSearchPaths(
668669
"setupModuleSearchPaths should be called before any queries are run");
669670

670671
std::string modRoot;
671-
if (!minimalModules) {
672+
if (moduleRoot.empty()) {
672673
modRoot = chplHome + "/modules";
673674
} else {
674-
modRoot = chplHome + "/modules/minimal";
675+
modRoot = moduleRoot;
676+
}
677+
if (minimalModules) {
678+
modRoot += "/minimal";
675679
}
676680

677681
std::string internal = modRoot + "/internal";
@@ -756,6 +760,7 @@ void setupModuleSearchPaths(
756760

757761

758762
void setupModuleSearchPaths(Context* context,
763+
const std::string& moduleRoot,
759764
bool minimalModules,
760765
bool enableTaskTracking,
761766
const std::vector<std::string>& cmdLinePaths,
@@ -770,6 +775,7 @@ void setupModuleSearchPaths(Context* context,
770775
auto chplModulePath = (it != chplEnv->end()) ? it->second : "";
771776
setupModuleSearchPaths(context,
772777
chplHomeStr,
778+
moduleRoot,
773779
minimalModules,
774780
chplEnv->at("CHPL_LOCALE_MODEL"),
775781
false,
@@ -783,6 +789,15 @@ void setupModuleSearchPaths(Context* context,
783789
inputFilenames);
784790
}
785791

792+
void setupModuleSearchPaths(Context* context,
793+
bool minimalModules,
794+
bool enableTaskTracking,
795+
const std::vector<std::string>& cmdLinePaths,
796+
const std::vector<std::string>& inputFilenames) {
797+
setupModuleSearchPaths(context, "", minimalModules, enableTaskTracking,
798+
cmdLinePaths, inputFilenames);
799+
}
800+
786801
bool
787802
filePathIsInInternalModule(Context* context, UniqueString filePath) {
788803
// check for prepended internal module paths that may have come in from

test/chplcheck/configfile-toml.chpl

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
configfile.chpl
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--config configfile.toml

test/chplcheck/configfile-toml.good

Whitespace-only changes.

test/chplcheck/configfile.chpl

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module configfile {
2+
for i in 1..10 {}
3+
}
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-c configfile.yaml

test/chplcheck/configfile.good

Whitespace-only changes.

test/chplcheck/configfile.toml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[tool.chplcheck]
2+
disable-rule = ["UnusedLoopIndex", "PascalCaseModules"]

test/chplcheck/configfile.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
disable-rule: ["UnusedLoopIndex", "PascalCaseModules"]

third-party/chpl-venv/chapel-py-requirements.txt

+2
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,7 @@ lsprotocol==2023.0.1
44
pygls==1.3.1
55
typeguard==4.3.0
66
ConfigArgParse==1.7
7+
PyYAML==6.0.2 # required by ConfigArgParse
8+
toml==0.10.2 # required by ConfigArgParse
79
# needed for <python3.11
810
exceptiongroup==1.2.2

tools/chapel-py/src/method-tables/core-methods.h

+10
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,16 @@ CLASS_BEGIN(Context)
5353
if (auto autoUseScope = resolution::scopeForAutoModule(node)) {
5454
std::ignore = resolution::resolveVisibilityStmts(node, autoUseScope, false);
5555
})
56+
METHOD(Context, _set_module_paths, "Set the module path arguments to the given lists of module paths and filenames, using a potentially different module root",
57+
void(std::string, std::vector<std::string>, std::vector<std::string>),
58+
59+
auto& modRoot = std::get<0>(args);
60+
auto& paths = std::get<1>(args);
61+
auto& filenames = std::get<2>(args);
62+
parsing::setupModuleSearchPaths(node, modRoot, false, false, paths, filenames);
63+
if (auto autoUseScope = resolution::scopeForAutoModule(node)) {
64+
std::ignore = resolution::resolveVisibilityStmts(node, autoUseScope, false);
65+
})
5666
METHOD(Context, is_bundled_path, "Check if the given file path is within the bundled (built-in) Chapel files",
5767
bool(chpl::UniqueString),
5868

tools/chpl-language-server/src/chpl-language-server.py

+45-12
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import re
4343
import sys
4444
import importlib.util
45+
import copy
4546

4647
import chapel
4748
from chapel.lsp import location_to_range, error_to_diagnostic
@@ -140,7 +141,6 @@
140141
CallHierarchyIncomingCall,
141142
)
142143

143-
import argparse
144144
import configargparse
145145
import sys
146146
import functools
@@ -671,9 +671,16 @@ def all(cls) -> Dict[str, "EndMarkerPattern"]:
671671

672672

673673
class ContextContainer:
674-
def __init__(self, file: str, config: Optional["WorkspaceConfig"]):
674+
def __init__(
675+
self,
676+
file: str,
677+
cls_config: "CLSConfig",
678+
config: Optional["WorkspaceConfig"],
679+
):
680+
self.cls_config: CLSConfig = cls_config
675681
self.config: Optional["WorkspaceConfig"] = config
676682
self.file_paths: List[str] = []
683+
self.std_module_root: str = ""
677684
self.module_paths: List[str] = [os.path.dirname(os.path.abspath(file))]
678685
self.context: chapel.Context = chapel.Context()
679686
self.file_infos: List["FileInfo"] = []
@@ -687,7 +694,12 @@ def __init__(self, file: str, config: Optional["WorkspaceConfig"]):
687694
self.module_paths = file_config["module_dirs"]
688695
self.file_paths = file_config["files"]
689696

690-
self.context.set_module_paths(self.module_paths, self.file_paths)
697+
self.std_module_root = self.cls_config.get("std_module_root")
698+
self.module_paths.extend(self.cls_config.get("module_dir"))
699+
700+
self.context._set_module_paths(
701+
self.std_module_root, self.module_paths, self.file_paths
702+
)
691703

692704
def register_signature(self, sig: chapel.TypedSignature) -> str:
693705
"""
@@ -734,7 +746,9 @@ def advance(self) -> List[Any]:
734746
"""
735747

736748
self.context.advance_to_next_revision(False)
737-
self.context.set_module_paths(self.module_paths, self.file_paths)
749+
self.context._set_module_paths(
750+
self.std_module_root, self.module_paths, self.file_paths
751+
)
738752

739753
with self.context.track_errors() as errors:
740754
for fi in self.file_infos:
@@ -1365,8 +1379,20 @@ def __init__(self):
13651379

13661380
def _construct_parser(self):
13671381
self.parser = configargparse.ArgParser(
1368-
default_config_files=[], # Empty for now because cwd() is odd with VSCode etc.
1369-
config_file_parser_class=configargparse.YAMLConfigFileParser,
1382+
default_config_files=[
1383+
os.path.join(os.getcwd(), "chpl-language-server.cfg"),
1384+
os.path.join(os.getcwd(), ".chpl-language-server.cfg"),
1385+
os.path.join(os.getcwd(), "Mason.toml"),
1386+
],
1387+
config_file_parser_class=configargparse.CompositeConfigParser(
1388+
[
1389+
configargparse.YAMLConfigFileParser,
1390+
configargparse.TomlConfigParser(
1391+
["tool.chpl-language-server"]
1392+
),
1393+
]
1394+
),
1395+
args_for_setting_config_path=["--config", "-c"],
13701396
)
13711397

13721398
def add_bool_flag(name: str, dest: str, default: bool):
@@ -1379,6 +1405,12 @@ def add_bool_flag(name: str, dest: str, default: bool):
13791405
self.parser.set_defaults(**{dest: default})
13801406

13811407
add_bool_flag("resolver", "resolver", False)
1408+
self.parser.add_argument(
1409+
"--std-module-root", default="", help=configargparse.SUPPRESS
1410+
)
1411+
self.parser.add_argument(
1412+
"--module-dir", "-M", action="append", default=[]
1413+
)
13821414
add_bool_flag("type-inlays", "type_inlays", True)
13831415
add_bool_flag("param-inlays", "param_inlays", True)
13841416
add_bool_flag("literal-arg-inlays", "literal_arg_inlays", True)
@@ -1401,25 +1433,25 @@ def _validate_end_markers(self):
14011433
valid_choices = ["all", "none"] + list(EndMarkerPattern.all().keys())
14021434
for marker in self.args["end_markers"]:
14031435
if marker not in valid_choices:
1404-
raise argparse.ArgumentError(
1436+
raise configargparse.ArgumentError(
14051437
None, f"Invalid end marker choice: {marker}"
14061438
)
14071439
n_markers = len(self.args["end_markers"])
14081440
if n_markers != len(set(self.args["end_markers"])):
1409-
raise argparse.ArgumentError(
1441+
raise configargparse.ArgumentError(
14101442
None, "Cannot specify the same end marker multiple times"
14111443
)
14121444
if "none" in self.args["end_markers"] and n_markers > 1:
1413-
raise argparse.ArgumentError(
1445+
raise configargparse.ArgumentError(
14141446
None, "Cannot specify 'none' with other end marker choices"
14151447
)
14161448
if "all" in self.args["end_markers"] and n_markers > 1:
1417-
raise argparse.ArgumentError(
1449+
raise configargparse.ArgumentError(
14181450
None, "Cannot specify 'all' with other end marker choices"
14191451
)
14201452

14211453
def parse_args(self):
1422-
self.args = vars(self.parser.parse_args())
1454+
self.args = copy.deepcopy(vars(self.parser.parse_args()))
14231455
self._parse_end_markers()
14241456
self._validate_end_markers()
14251457

@@ -1431,6 +1463,7 @@ class ChapelLanguageServer(LanguageServer):
14311463
def __init__(self, config: CLSConfig):
14321464
super().__init__("chpl-language-server", "v0.1")
14331465

1466+
self.config: CLSConfig = config
14341467
self.contexts: Dict[str, ContextContainer] = {}
14351468
self.context_ids: Dict[ContextContainer, str] = {}
14361469
self.context_id_counter = 0
@@ -1530,7 +1563,7 @@ def get_context(self, uri: str) -> ContextContainer:
15301563
if path in self.contexts:
15311564
return self.contexts[path]
15321565

1533-
context = ContextContainer(path, workspace_config)
1566+
context = ContextContainer(path, self.config, workspace_config)
15341567
for file in context.file_paths:
15351568
self.contexts[file] = context
15361569
self.contexts[path] = context

0 commit comments

Comments
 (0)