diff --git a/.github/workflows/daily-smoke-tests.yml b/.github/workflows/daily-smoke-tests.yml index 45b5aa8e0..6cb870b6d 100644 --- a/.github/workflows/daily-smoke-tests.yml +++ b/.github/workflows/daily-smoke-tests.yml @@ -13,4 +13,4 @@ jobs: uses: ./.github/workflows/run-tests.yml with: node-matrix: "[{version: 'lts/*', artifact: 'lts'}, {version: 'latest', artifact: 'latest'}]" - branch: dev + target-branch: dev diff --git a/messages/config-command.md b/messages/config-command.md index 8d32829e5..b49e35adf 100644 --- a/messages/config-command.md +++ b/messages/config-command.md @@ -4,11 +4,11 @@ Display the current state of configuration for Code Analyzer. # command.description -Code Analyzer gives you the ability to configure settings that modify Code Analyzer's behavior, to override the tags and severity levels of rules, and to configure the engine specific settings. Use this command to see the current state of this configuration. You can also save this state to a YAML-formatted file that you can modify for your needs. +Code Analyzer gives you the ability to configure settings that modify Code Analyzer's behavior, to override the tags and severity levels of rules, and to configure the engine specific settings. Use this command to see the current state of this configuration. You can also save this state to a YAML-formatted file that you can modify for your needs. To apply a custom configuration with Code Analyzer, either keep your custom configuration settings in a `code-analyzer.yml` file located in the current folder from which you are executing commands, or specify the location of your custom configuration file to the Code Analyzer commands with the --config-file flag. -We're continually improving Salesforce Code Analyzer. Tell us what you think! Give feedback at http://sfdc.co/CodeAnalyzerFeedback. +We're continually improving Salesforce Code Analyzer. Tell us what you think! Give feedback at https://sfdc.co/CodeAnalyzerFeedback. # command.examples @@ -28,9 +28,9 @@ We're continually improving Salesforce Code Analyzer. Tell us what you think! Gi <%= config.bin %> <%= command.id %> --rule-selector Recommended -- Display the configuration state associated with all the rules that are applicable to your workspace folder, `./src`: +- Display the configuration state associated with all the rules that are applicable to the files targeted within the folder `./src`: - <%= config.bin %> <%= command.id %> --workspace ./src + <%= config.bin %> <%= command.id %> --target ./src - Display any relevant configuration settings associated with the rule name 'no-undef' from the 'eslint' engine: @@ -42,15 +42,31 @@ We're continually improving Salesforce Code Analyzer. Tell us what you think! Gi # flags.workspace.summary -Set of files you want to include in the code analysis. +Set of files that make up your workspace. # flags.workspace.description -Use the --workspace flag to display only the configuration associated with the rules that apply to the files that make up your workspace. Typically, a workspace is a single project folder that contains all your files. But it can also consist of one or more folders, one or more files, and use glob patterns (wildcards). If you specify this flag multiple times, then your workspace is the sum of the files and folders. +Use the `--workspace` flag to display only the configuration associated with the rules that apply to the files that make up your workspace. Typically, a workspace is a single project folder that contains all your files. But it can also consist of one or more folders, one or more files, and use glob patterns (wildcards). If you specify this flag multiple times, then your workspace is the sum of the files and folders. -This command uses the type of file in the workspace, such as JavaScript or Typescript, to determine the rules to include in the configuration state. For example, if your workspace contains only JavaScript files, the command doesn't include TypeScript rules. The command uses a file's extension to determine what kind of file it is, such as ".ts" for TypeScript. +This command uses the types of files in the workspace, such as JavaScript or Typescript, to determine the applicable configuration state. For example, if your workspace contains only JavaScript files, then the command doesn't display configuration state associated with TypeScript rules. The command uses a file's extension to determine what kind of file it is, such as ".ts" for TypeScript. -Some engines may be configured to add additional rules based on what it finds in your workspace. For example, if you set the "engines.eslint.auto_discover_eslint_config" value of your `code-analyzer.yml` file to true, then supplying your workspace allows the "eslint" engine to examine your files in order to find ESLint configuration files that could potentially add in additional rules. +Some engines can be configured to add additional rules based on what it finds in your workspace. For example, if you set the engines.eslint.auto_discover_eslint_config value of your `code-analyzer.yml` file to true, then supplying your workspace allows the "eslint" engine to examine your files in order to find ESLint configuration files that could potentially add in additional rules. + +If you specify `--target` but not `--workspace`, then the current folder '.' is used as your workspace. + +# flags.target.summary + +Subset of files within your workspace that you want to target for analysis. + +# flags.target.description + +Use the `--target` flag to display the configuration state associated with the rules that apply to only a subset of targeted files within your workspace. You can specify a target as a file, a folder, or a glob pattern. If you specify this flag multiple times, then the full list of targeted files is the sum of the files and folders. + +The command uses the type of the targeted files, such as JavaScript or Typescript, to determine which configuration state is applicable. For example, if you target only JavaScript files, then the command doesn't display the configuration state associated with TypeScript rules. The command uses a file's extension to determine what kind of file it is, such as ".ts" for TypeScript. + +Each targeted file must live within the workspace specified by the `–-workspace` flag. + +If you specify `--workspace` but not `--target`, then all the files within your workspace are targeted. # flags.rule-selector.summary @@ -58,13 +74,13 @@ Selection of rules, based on engine name, severity level, rule name, tag, or a c # flags.rule-selector.description -Use the --rule-selector flag to display only the configuration associated with the rules based on specific criteria. You can select by engine, such as the rules associated with the "retire-js" or "eslint" engine. Or select by the severity of the rules, such as high or moderate. You can also select rules using tag values or rule names. +Use the `--rule-selector` flag to display only the configuration associated with the rules based on specific criteria. You can select by engine, such as the rules associated with the "retire-js" or "eslint" engine. Or select by the severity of the rules, such as high or moderate. You can also select rules using tag values or rule names. -You can combine different criteria using colons to further filter the list; the colon works as an intersection. For example, "--rule-selector eslint:Security" reduces the output to only contain the configuration state associated with the rules from the "eslint" engine that have the "Security" tag. To add multiple rule selectors together (a union), specify the --rule-selector flag multiple times, such as "--rule-selector eslint:Recommended --rule-selector retire-js:3". +You can combine different criteria using colons to further filter the list; the colon works as an intersection. For example, `--rule-selector eslint:Security` reduces the output to only contain the configuration state associated with the rules from the "eslint" engine that have the "Security" tag. To add multiple rule selectors together (a union), specify the `--rule-selector` flag multiple times, such as `--rule-selector eslint:Recommended --rule-selector retire-js:3`. If you don't specify this flag, then the command uses the "all" rule selector. -Run `<%= config.bin %> <%= command.id %> --rule-selector Recommended` to display the configuration state associated with just the 'Recommended' rules, instead of all the rules. +Run `<%= config.bin %> <%= command.id %> --rule-selector Recommended` to display the configuration state associated with just the 'Recommended' rules, instead of all the rules. # flags.config-file.summary @@ -82,4 +98,6 @@ Output file to write the configuration state to. The file is written in YAML for # flags.output-file.description -Use this flag to write the final config to a file, in addition to the terminal. +If you specify a file within folder, such as `--output-file ./config/code-analyzer.yml`, the folder must already exist, or you get an error. If the file already exists, a prompt asks if you want to overwrite it. + +If you don't specify this flag, the command outputs the configuration state to the terminal. diff --git a/messages/path-start-util.md b/messages/path-start-util.md deleted file mode 100644 index e40d93bcf..000000000 --- a/messages/path-start-util.md +++ /dev/null @@ -1,7 +0,0 @@ -# error.glob-method-conflict - -Path start point %s is invalid: You can't use glob patterns when specifying a method with the #methodName syntax. - -# error.negative-globs-unsupported - -Path start point %s is invalid: Negative globs are unsupported. diff --git a/messages/rules-command.md b/messages/rules-command.md index 1bdaa1301..71eac2183 100644 --- a/messages/rules-command.md +++ b/messages/rules-command.md @@ -8,11 +8,11 @@ You can also view details about the rules, such as the engine it's associated wi Use this command to determine the exact set of rules to analyze your code. The `code-analyzer run` command has similar flags as this command, so once you've determined the flag values for this command that list the rules you want to run, you specify the same values to the `code-analyzer run` command. -We're continually improving Salesforce Code Analyzer. Tell us what you think! Give feedback at http://sfdc.co/CodeAnalyzerFeedback. +We're continually improving Salesforce Code Analyzer. Tell us what you think! Give feedback at https://sfdc.co/CodeAnalyzerFeedback. # command.examples -- List rules using the default behavior: include rules from all engines that have a "Recommended" tag; display the rules using concise table format; and automatically apply rule or engine overrides if a "code-analyzer.yml" or "code-analyzer.yaml" file exists in the current folder: +- List rules using the default behavior: include rules from all engines that have a "Recommended" tag; display the rules using concise table format; and automatically apply rule or engine overrides if a `code-analyzer.yml` or `code-analyzer.yaml` file exists in the current folder: <%= config.bin %> <%= command.id %> @@ -22,33 +22,33 @@ We're continually improving Salesforce Code Analyzer. Tell us what you think! Gi - List the recommended rules for the "eslint" engine: - <%= config.bin %> <%= command.id %> --rule-selector eslint:Recommended + <%= config.bin %> <%= command.id %> --rule-selector eslint:Recommended - List all the rules for the "eslint" engine: - <%= config.bin %> <%= command.id %> --rule-selector eslint + <%= config.bin %> <%= command.id %> --rule-selector eslint - The previous example is equivalent to this example: - <%= config.bin %> <%= command.id %> --rule-selector eslint:all + <%= config.bin %> <%= command.id %> --rule-selector eslint:all - List the details about all rules for all engines; also write the rules in JSON format to a file called "rules.json" in the "out" folder, which must already exist: - <%= config.bin %> <%= command.id %> --rule-selector all --output-file ./out/rules.json --view detail + <%= config.bin %> <%= command.id %> --rule-selector all --output-file ./out/rules.json --view detail - Get a more accurate list of the rules that apply specifically to your workspace (all the files in the current folder): - <%= config.bin %> <%= command.id %> --rule-selector all --workspace . + <%= config.bin %> <%= command.id %> --rule-selector all --workspace . -- List the recommended rules associated with a workspace that includes all the files in the folder "./other-source" and only the Apex class files (extension .cls) under the folder "./force-app": +- List the recommended rules associated with a workspace that targets all the files in the folder "./other-source" and only the Apex class files (extension .cls) under the folder "./force-app": - <%= config.bin %> <%= command.id %> --rule-selector Recommended --workspace ./other-source --workspace ./force-app/**/*.cls + <%= config.bin %> <%= command.id %> --rule-selector Recommended --workspace . --target ./other-source --target ./force-app/**/*.cls - List all the "eslint" engine rules that have a moderate severity (3) and the recommended "retire-js" engine rules with any severity: <%= config.bin %> <%= command.id %> --rule-selector eslint:3 --rule-selector retire-js:Recommended -- Similar to the previous example, but apply the rule overrides and engine settings from the configuration file called "code-analyzer2.yml" in the current folder. If, for example, you changed the severity of an "eslint" rule from moderate (3) to high (2) in the configuration file, then that rule won't be listed: +- Similar to the previous example, but apply the rule overrides and engine settings from the configuration file called `code-analyzer2.yml` in the current folder. If, for example, you changed the severity of an "eslint" rule from moderate (3) to high (2) in the configuration file, then that rule isn't listed: <%= config.bin %> <%= command.id %> --rule-selector eslint:3 --rule-selector retire-js:Recommended --config-file ./code-analyzer2.yml @@ -56,21 +56,37 @@ We're continually improving Salesforce Code Analyzer. Tell us what you think! Gi <%= config.bin %> <%= command.id %> --rule-selector eslint:getter-return --rule-selector no-inner-declarations --view detail -- List the details of the recommended "eslint" engine rules that have the tag "problem" and high severity level (2) that apply to your workspace folder "./force-app": +- List the details of the recommended "eslint" engine rules that have the tag "problem" and high severity level (2) that apply when targeting the files within the folder "./force-app": - <%= config.bin %> <%= command.id %> --rule-selector eslint:Recommended:problem:2 --view detail --workspace ./force-app + <%= config.bin %> <%= command.id %> --rule-selector eslint:Recommended:problem:2 --view detail --target ./force-app # flags.workspace.summary -Set of files you want to include in the code analysis. +Set of files that make up your workspace. # flags.workspace.description -If you specify this flag, the command returns a more accurate list of the rules that apply to the files that make up your workspace. Typically, a workspace is a single project folder that contains all your files. But it can also consist of one or more folders, one or more files, and use glob patterns (wildcards). If you specify this flag multiple times, then your workspace is the sum of the files and folders. +Use the `--workspace` flag to return a more accurate list of the rules that apply to the files that make up your workspace. Typically, a workspace is a single project folder that contains all your files. But it can also consist of one or more folders, one or more files, and use glob patterns (wildcards). If you specify this flag multiple times, then your workspace is the sum of the files and folders. -This command uses the type of file in the workspace, such as JavaScript or Typescript, to determine the rules to list. For example, if your workspace contains only JavaScript files, the command doesn't list TypeScript rules. The command uses a file's extension to determine what kind of file it is, such as ".ts" for TypeScript. +The command uses the types of files in the workspace, such as JavaScript or Typescript, to determine which rules to list. For example, if your workspace contains only JavaScript files, the command doesn't list TypeScript rules. The command uses a file's extension to determine what kind of file it is, such as ".ts" for TypeScript. -Some engines may be configured to add additional rules based on what it finds in your workspace. For example, if you set the engines.eslint.auto_discover_eslint_config value of your code-analyzer.yml file to true, then supplying your workspace allows the "eslint" engine to examine your files in order to find ESLint configuration files that could potentially add in additional rules. +Some engines may be configured to add additional rules based on what it finds in your workspace. For example, if you set the engines.eslint.auto_discover_eslint_config value of your `code-analyzer.yml` file to true, then supplying your workspace allows the "eslint" engine to examine your files in order to find ESLint configuration files that could potentially add in additional rules. + +If you specify `--target` but not `--workspace`, then the current folder '.' is used as your workspace. + +# flags.target.summary + +Subset of files within your workspace that you want to target for analysis. + +# flags.target.description + +Use the `--target` flag to return a more accurate list of the rules that apply to only a subset of targeted files within your workspace. You can specify a target as a file, a folder, or a glob pattern. If you specify this flag multiple times, then the full list of targeted files is the sum of the files and folders. + +The command uses the type of the targeted files, such as JavaScript or Typescript, to determine which rules to list. For example, if you target only JavaScript files, the command doesn't list TypeScript rules. The command uses a file's extension to determine what kind of file it is, such as ".ts" for TypeScript. + +Each targeted file must live within the workspace specified by the –-workspace flag. + +If you specify `--workspace` but not `--target`, then all the files within your workspace are targeted. # flags.rule-selector.summary @@ -78,11 +94,11 @@ Selection of rules, based on engine name, severity level, rule name, tag, or a c # flags.rule-selector.description -Use the --rule-selector flag to select the list of rules based on specific criteria. For example, you can select by engine, such as the rules associated with the "retire-js" or "eslint" engine. Or select by the severity of the rules, such as high or moderate. You can also select rules using tag values or rule names. Every rule has a name, which is unique within the scope of an engine. Most rules have tags, although it's not required. An example of a tag is "Recommended". +Use the `--rule-selector` flag to select the list of rules based on specific criteria. For example, you can select by engine, such as the rules associated with the "retire-js" or "eslint" engine. Or select by the severity of the rules, such as high or moderate. You can also select rules using tag values or rule names. Every rule has a name, which is unique within the scope of an engine. Most rules have tags, although it's not required. An example of a tag is "Recommended". -You can combine different criteria using colons to further filter the list; the colon works as an intersection. For example, "--rule-selector eslint:Security" lists rules associated only with the "eslint" engine that have the Security tag. The flag "--rule-selector eslint:Security:3" flag lists the "eslint" rules that have the Security tag and moderate severity (3). To add multiple rule selectors together (a union), specify the --rule-selector flag multiple times, such as "--rule-selector eslint:Recommended --rule-selector retire-js:3". +You can combine different criteria using colons to further filter the list; the colon works as an intersection. For example, `--rule-selector eslint:Security` lists rules associated only with the "eslint" engine that have the Security tag. The flag `--rule-selector eslint:Security:3` flag lists the "eslint" rules that have the Security tag and moderate severity (3). To add multiple rule selectors together (a union), specify the `--rule-selector` flag multiple times, such as `--rule-selector eslint:Recommended --rule-selector retire-js:3`. -Run `<%= config.bin %> <%= command.id %> --rule-selector all` to list see the possible values for engine name, rule name, tags, and severity levels that you can use with this flag. +Run `<%= config.bin %> <%= command.id %> --rule-selector all` to list the possible values for engine name, rule name, tags, and severity levels that you can use with this flag. # flags.config-file.summary @@ -92,7 +108,7 @@ Path to the configuration file used to customize the engines and rules. Code Analyzer has an internal default configuration for its rule and engine properties. If you want to override these defaults, you can create a Code Analyzer configuration file. -We recommend that you name your Code Analyzer configuration file "code-analyzer.yml" or "code-analyzer.yaml" and put it at the root of your workspace. You then don't need to use this flag when you run the `<%= command.id %>` command from the root of your workspace, because it automatically looks for either file in the current folder, and if found, applies its rule overrides and engine settings. If you want to name the file something else, or put it in an alternative folder, then you must specify this flag. +We recommend that you name your Code Analyzer configuration file `code-analyzer.yml` or `code-analyzer.yaml` and put it at the root of your workspace. You then don't need to use this flag when you run the `<%= command.id %>` command from the root of your workspace, because it automatically looks for either file in the current folder, and if found, applies its rule overrides and engine settings. If you want to name the file something else, or put it in an alternative folder, then you must specify this flag. To help you get started, use the `code-analyzer config` command to create your first Code Analyzer configuration file. With it, you can change the severity of an existing rule, change a rule's tags, and so on. Then use this flag to specify the file so that the command takes your customizations into account. @@ -104,7 +120,7 @@ Format to display the rules in the terminal. The format `table` is concise and shows minimal output, the format `detail` shows all available information. -If you specify neither --view nor --output-file, then the default table view is shown. If you specify --output-file but not --view, only summary information is shown in the terminal. +If you specify neither `--view` nor `--output-file`, then the default table view is shown. If you specify `--output-file` but not `--view`, only summary information is shown in the terminal. # flags.output-file.summary @@ -112,6 +128,6 @@ Name of the file where the selected rules are written. The file format depends o # flags.output-file.description -If you specify a folder, such as "--output-file ./out/rules.json", the folder must already exist or you get an error. If the file already exists, it's overwritten without prompting. +If you specify a file within folder, such as `--output-file ./out/rules.json`, the folder must already exist, or you get an error. If the file already exists, it's overwritten without prompting. If you don't specify this flag, the command outputs the rules to only the terminal. diff --git a/messages/run-command.md b/messages/run-command.md index 2f0e241d4..ffcbac212 100644 --- a/messages/run-command.md +++ b/messages/run-command.md @@ -6,19 +6,19 @@ Analyze your code with a selection of rules to ensure good coding practices. You can scan your codebase with the recommended rules. Or use flags to filter the rules based on engines (such as "retire-js" or "eslint"), rule names, tags, and more. -If you want to preview the list of rules before you actually run them, use the `code-analyzer rules` command, which also has the "--rules-selector", "--workspace", and "--config-file" flags that together define the list of rules to be run. +If you want to preview the list of rules before you actually run them, use the `code-analyzer rules` command, which also has the `--config-file`, `--rule-selector`, `--target`, and `--workspace` flags that together define the list of rules to be run. -We're continually improving Salesforce Code Analyzer. Tell us what you think! Give feedback at http://sfdc.co/CodeAnalyzerFeedback. +We're continually improving Salesforce Code Analyzer. Tell us what you think! Give feedback at https://sfdc.co/CodeAnalyzerFeedback. # command.examples -- Analyze code using the default behavior: analyze the files in the current folder (default workspace) using the Recommended rules; display the output in the terminal with the concise table view; and automatically apply rule or engine overrides if a "code-analyzer.yml" or "code-analyzer.yaml" file exists in the current folder: +- Analyze code using the default behavior: analyze all the files in the current folder (default workspace) using the Recommended rules; display the output in the terminal with the concise table view; and automatically apply rule or engine overrides if a `code-analyzer.yml` or `code-analyzer.yaml` file exists in the current folder: <%= config.bin %> <%= command.id %> - The previous example is equivalent to this example: - <%= config.bin %> <%= command.id %> --rule-selector Recommended --workspace . --view table --config-file ./code-analyzer.yml + <%= config.bin %> <%= command.id %> --rule-selector Recommended --workspace . --target . --view table --config-file ./code-analyzer.yml - Analyze the files using the recommended "eslint" rules and show details of the violations: @@ -30,15 +30,15 @@ We're continually improving Salesforce Code Analyzer. Tell us what you think! Gi - The previous example is equivalent to this example: - <%= config.bin %> <%= command.id %> --rule-selector eslint:all + <%= config.bin %> <%= command.id %> --rule-selector eslint:all - Analyze the files using all rules for all engines: <%= config.bin %> <%= command.id %> --rule-selector all -- Analyze files using the recommended "retire-js" rules analyze in a workspace that consists of all files in the folder "./other-source" and only the Apex class files (extension .cls) in the folder "./force-app": +- Analyze files using the recommended "retire-js" rules; target all the files in the folder "./other-source" and only the Apex class files (extension .cls) in the folder "./force-app": - <%= config.bin %> <%= command.id %> --rule-selector retire-js:Recommended --workspace ./other-source --workspace ./force-app/**/*.cls + <%= config.bin %> <%= command.id %> --rule-selector retire-js:Recommended --target ./other-source --target ./force-app/**/*.cls - Specify a custom configuration file and output the results to the "results.csv" file in CSV format; the commands fails if it finds a violation that exceeds the moderate severity level (3): @@ -52,35 +52,31 @@ We're continually improving Salesforce Code Analyzer. Tell us what you think! Gi <%= config.bin %> <%= command.id %> --rule-selector eslint:getter-return --rule-selector no-inner-declarations -# command.examples.temporarily-hidden-for-now - -- Specify three method starting points to be applied to the path-based recommended rules of the Salesforce Graph Engine, "sfge", while specifying the "./src" folder as the workspace to be used to build the source graph: - - <%= config.bin %> <%= command.id %> --rule-selector sfge:Recommended --workspace ./src --path-start ./src/classes/Utils.cls#init --path-start ./src/classes/Helpers.cls#method1;method2 - # flags.workspace.summary -Set of files you want to include in the code analysis. +Set of files that make up your workspace. # flags.workspace.description -Typically, a workspace is a single project folder that contains all your files. But it can also consist of one or more folders, one or more files, and use glob patterns (wildcards). If you specify this flag multiple times, then your workspace is the sum of the files and folders. +Typically, a workspace is a single project folder that contains all your files. But it can also consist of one or more folders, one or more files, and use glob patterns (wildcards). If you specify this flag multiple times, then your workspace is the sum of the files and folders. + +Some engines often need your entire code base to perform an analysis, even if you want to target only a subset of the files within your workspace , such as with the `--target` flag. For example, the Salesforce Graph Engine might need to compile your entire project in order to properly build a graph so it can perform a data flow analysis on the paths that start in your targeted files. -# flags.path-start.summary +If you don't specify the `--workspace` flag, then the current folder '.' is used as your workspace. -Starting points within your workspace to restrict any path-based analysis rules to. +# flags.target.summary -# flags.path-start.description +Subset of files within your workspace to be targeted for analysis. -If you don't specify this flag, then any path-based analysis rules automatically discover and use all starting points found in your workspace. Use this flag to restrict the starting points to only those you want in your code analysis. +# flags.target.description -This flag only applies to path-based analysis rules, which are of type DataFlow and Flow. These types of rules are only available from some engines, like the Salesforce Graph Engine, "sfge" for example. +You can specify a target as a file, a folder, or a glob pattern. -If you specify a file or a folder as your starting point, then the analysis uses only the methods that have public or global accessibility. +If you specify this flag multiple times, then the full list of targeted files is the sum of the files and folders. -To specify individual methods as a starting point, use the syntax "#methodName" to select a single method or "#methodName1;methodName2" to select multiple methods. For example, "SomeClass.cls#method1" (single method) or "SomeClass.cls#method1;method2" (multiple methods). +Each targeted file must live within the workspace that you specified with the `–-workspace` flag. -You can use glob patterns (wildcards) only when specifying files and folders; you can't use glob patterns when specifying individual methods. +If you don't specify the `--target` flag, then all the files within your workspace (specified by the `--workspace` flag) are targeted for analysis. # flags.rule-selector.summary @@ -88,9 +84,9 @@ Selection of rules, based on engine name, severity level, rule name, tag, or a c # flags.rule-selector.description -Use the --rule-selector flag to select the list of rules to run based on specific criteria. For example, you can select by engine, such as the rules associated with the "retire-js" or "eslint" engine. Or select by the severity of the rules, such as high or moderate. You can also select rules using tag values or rule names. Every rule has a name, which is unique within the scope of an engine. Most rules have tags, although it's not required. An example of a tag is "Recommended". +Use the `--rule-selector` flag to select the list of rules to run based on specific criteria. For example, you can select by engine, such as the rules associated with the "retire-js" or "eslint" engine. Or select by the severity of the rules, such as high or moderate. You can also select rules using tag values or rule names. Every rule has a name, which is unique within the scope of an engine. Most rules have tags, although it's not required. An example of a tag is "Recommended". -You can combine different criteria using colons to further filter the list; the colon works as an intersection. For example, "--rule-selector eslint:Security" runs rules associated only with the "eslint" engine that have the Security tag. The flag "--rule-selector eslint:Security:3" flag runs the "eslint" rules that have the Security tag and moderate severity (3). To add multiple rule selectors together (a union), specify the --rule-selector flag multiple times, such as "--rule-selector eslint:Recommended --rule-selector retire-js:3". +You can combine different criteria using colons to further filter the list; the colon works as an intersection. For example, `--rule-selector eslint:Security` runs rules associated only with the "eslint" engine that have the Security tag. The flag `--rule-selector eslint:Security:3` flag runs the "eslint" rules that have the Security tag and moderate severity (3). To add multiple rule selectors together (a union), specify the `--rule-selector` flag multiple times, such as `--rule-selector eslint:Recommended --rule-selector retire-js:3`. Run `<%= config.bin %> code-analyzer rules --rule-selector all` to see the possible values for engine name, rule name, tags, and severity levels that you can use with this flag. @@ -110,7 +106,7 @@ Path to the configuration file used to customize the engines and rules. Code Analyzer has an internal default configuration for its rule and engine properties. If you want to override these defaults, you can create a Code Analyzer configuration file. -We recommend that you name your Code Analyzer configuration file "code-analyzer.yml" or "code-analyzer.yaml" and put it at the root of your workspace. You then don't need to use this flag when you run the `<%= command.id %>` command from the root of your workspace, because it automatically looks for either file in the current folder, and if found, applies its rule overrides and engine settings. If you want to name the file something else, or put it in an alternative folder, then you must specify this flag. +We recommend that you name your Code Analyzer configuration file `code-analyzer.yml` or `code-analyzer.yaml` and put it at the root of your workspace. You then don't need to use this flag when you run the `<%= command.id %>` command from the root of your workspace, because it automatically looks for either file in the current folder, and if found, applies its rule overrides and engine settings. If you want to name the file something else, or put it in an alternative folder, then you must specify this flag. To help you get started, use the `code-analyzer config` command to create your first Code Analyzer configuration file. With it, you can change the severity of an existing rule, change a rule's tags, and so on. Then use this flag to specify the file so that the command takes your customizations into account. @@ -122,7 +118,7 @@ Format to display the command results in the terminal. The format `table` is concise and shows minimal output, the format `detail` shows all available information. -If you specify neither --view nor --output-file, then the default table view is shown. If you specify --output-file but not --view, only summary information is shown. +If you specify neither `--view` nor `--output-file`, then the default table view is shown. If you specify `--output-file` but not `--view`, only summary information is shown. # flags.output-file.summary @@ -130,7 +126,7 @@ Name of the file where the analysis results are written. The file format depends # flags.output-file.description -If you don't specify this flag, the command outputs the results to only the terminal. Use this flag to print the results to a file; the format of the results depends on the extension you provide. For example, "--output-file results.csv" creates a comma-separated values file. You can specify one of these extensions: +If you don't specify this flag, the command outputs the results to only the terminal. Use this flag to print the results to a file; the format of the results depends on the extension you provide. For example, `--output-file results.csv` creates a comma-separated values file. You can specify one of these extensions: - .csv - .html or .htm @@ -138,9 +134,9 @@ If you don't specify this flag, the command outputs the results to only the term - .sarif or .sarif.json - .xml -To output the results to multiple files, specify this flag multiple times. For example: "--output-file results.json --output-file report.html" creates both a JSON results file and an HTML file. +To output the results to multiple files, specify this flag multiple times. For example: `--output-file results.json --output-file report.html` creates both a JSON results file and an HTML file. -If you specify a folder, such as "--output-file ./out/results.json", the folder must already exist or you get an error. If the file already exists, it's overwritten without prompting. +If you specify a file within a folder, such as `--output-file ./out/results.json`, the folder must already exist, or you get an error. If the file already exists, it's overwritten without prompting. # error.invalid-severity-threshold diff --git a/messages/workspace-util.md b/messages/workspace-util.md index 57b4ac1fe..8a27f59b9 100644 --- a/messages/workspace-util.md +++ b/messages/workspace-util.md @@ -1,3 +1,3 @@ # error.negative-globs-unsupported -Workspace path %s is invalid: Negative globs are unsupported. +%s path %s is invalid: Negative globs are unsupported. diff --git a/package.json b/package.json index 9a52ec84d..6ef0dc638 100644 --- a/package.json +++ b/package.json @@ -6,14 +6,14 @@ "bugs": "https://github.com/forcedotcom/sfdx-scanner/issues", "dependencies": { "@oclif/core": "3.27.0", - "@salesforce/code-analyzer-core": "0.25.1", - "@salesforce/code-analyzer-engine-api": "0.20.0", - "@salesforce/code-analyzer-eslint-engine": "0.20.1", - "@salesforce/code-analyzer-flowtest-engine": "0.18.1", - "@salesforce/code-analyzer-pmd-engine": "0.21.0", - "@salesforce/code-analyzer-regex-engine": "0.18.1", - "@salesforce/code-analyzer-retirejs-engine": "0.18.1", - "@salesforce/code-analyzer-sfge-engine": "0.1.0", + "@salesforce/code-analyzer-core": "0.26.0", + "@salesforce/code-analyzer-engine-api": "0.21.0", + "@salesforce/code-analyzer-eslint-engine": "0.21.0", + "@salesforce/code-analyzer-flow-engine": "0.19.0", + "@salesforce/code-analyzer-pmd-engine": "0.22.0", + "@salesforce/code-analyzer-regex-engine": "0.19.0", + "@salesforce/code-analyzer-retirejs-engine": "0.19.0", + "@salesforce/code-analyzer-sfge-engine": "0.2.0", "@salesforce/core": "6.7.6", "@salesforce/sf-plugins-core": "5.0.13", "@salesforce/ts-types": "^2.0.12", diff --git a/src/commands/code-analyzer/config.ts b/src/commands/code-analyzer/config.ts index d7dc16643..b193ac045 100644 --- a/src/commands/code-analyzer/config.ts +++ b/src/commands/code-analyzer/config.ts @@ -25,6 +25,13 @@ export default class ConfigCommand extends SfCommand implements Displayabl multiple: true, delimiter: ',' }), + target: Flags.string({ + summary: getMessage(BundleName.ConfigCommand, 'flags.target.summary'), + description: getMessage(BundleName.ConfigCommand, 'flags.target.description'), + char: 't', + multiple: true, + delimiter: ',' + }), 'rule-selector': Flags.string({ summary: getMessage(BundleName.ConfigCommand, 'flags.rule-selector.summary'), description: getMessage(BundleName.ConfigCommand, 'flags.rule-selector.description'), diff --git a/src/commands/code-analyzer/rules.ts b/src/commands/code-analyzer/rules.ts index eda15996e..06308fcaa 100644 --- a/src/commands/code-analyzer/rules.ts +++ b/src/commands/code-analyzer/rules.ts @@ -26,6 +26,13 @@ export default class RulesCommand extends SfCommand implements Displayable multiple: true, delimiter: ',', }), + target: Flags.string({ + summary: getMessage(BundleName.RulesCommand, 'flags.target.summary'), + description: getMessage(BundleName.RulesCommand,'flags.target.description'), + char: 't', + multiple: true, + delimiter: ',' + }), 'rule-selector': Flags.string({ summary: getMessage(BundleName.RulesCommand, 'flags.rule-selector.summary'), description: getMessage(BundleName.RulesCommand, 'flags.rule-selector.description'), @@ -66,6 +73,7 @@ export default class RulesCommand extends SfCommand implements Displayable 'output-file': outputFiles, 'rule-selector': parsedFlags['rule-selector'], 'workspace': parsedFlags['workspace'], + 'target': parsedFlags['target'] }; await action.execute(rulesInput); @@ -82,7 +90,7 @@ export default class RulesCommand extends SfCommand implements Displayable viewer: this.createRulesViewer(view, outputFiles, uxDisplay), writer: CompositeRulesWriter.fromFiles(outputFiles) }; - + return dependencies; } diff --git a/src/commands/code-analyzer/run.ts b/src/commands/code-analyzer/run.ts index 47a460e5e..838c39660 100644 --- a/src/commands/code-analyzer/run.ts +++ b/src/commands/code-analyzer/run.ts @@ -29,13 +29,12 @@ export default class RunCommand extends SfCommand implements Displayable { delimiter: ',', default: ['.'] }), - 'path-start': Flags.string({ - summary: getMessage(BundleName.RunCommand, 'flags.path-start.summary'), - description: getMessage(BundleName.RunCommand, 'flags.path-start.description'), - char: 's', + 'target': Flags.string({ + summary: getMessage(BundleName.RunCommand, 'flags.target.summary'), + description: getMessage(BundleName.RunCommand, 'flags.target.description'), + char: 't', multiple: true, - delimiter: ',', - hidden: true + delimiter: ',' }), // === Flags pertaining to rule selection === 'rule-selector': Flags.string({ @@ -50,7 +49,7 @@ export default class RunCommand extends SfCommand implements Displayable { 'severity-threshold': Flags.string({ summary: getMessage(BundleName.RunCommand, 'flags.severity-threshold.summary'), description: getMessage(BundleName.RunCommand, 'flags.severity-threshold.description'), - char: 't' + char: 's' }), view: Flags.string({ summary: getMessage(BundleName.RunCommand, 'flags.view.summary'), @@ -81,11 +80,11 @@ export default class RunCommand extends SfCommand implements Displayable { const runInput: RunInput = { 'config-file': parsedFlags['config-file'], 'output-file': parsedFlags['output-file'] ?? [], - 'path-start': parsedFlags['path-start'], // TODO: We should move validation of this here instead of having it in the RunAction. 'rule-selector': parsedFlags['rule-selector'], 'workspace': parsedFlags['workspace'], 'severity-threshold': parsedFlags['severity-threshold'] === undefined ? undefined : - convertThresholdToEnum(parsedFlags['severity-threshold'].toLowerCase()) + convertThresholdToEnum(parsedFlags['severity-threshold'].toLowerCase()), + 'target': parsedFlags['target'] }; await action.execute(runInput); } diff --git a/src/lib/actions/ConfigAction.ts b/src/lib/actions/ConfigAction.ts index 2b4c95d53..1b8f77bb5 100644 --- a/src/lib/actions/ConfigAction.ts +++ b/src/lib/actions/ConfigAction.ts @@ -26,6 +26,7 @@ export type ConfigInput = { 'output-file'?: string; 'rule-selector': string[]; workspace?: string[]; + target?: string[]; }; export class ConfigAction { @@ -100,14 +101,15 @@ export class ConfigAction { // ==== PERFORM RULE SELECTIONS ================================================================================ - const userSelectOptions = input.workspace - ? {workspace: await createWorkspace(userCore, input.workspace)} + const workspace: string[]|undefined = input.workspace || (input.target ? ['.'] : undefined); + const userSelectOptions = workspace + ? {workspace: await createWorkspace(userCore, workspace, input.target)} : undefined; - const defaultSelectOptionsForAllRules = input.workspace - ? {workspace: await createWorkspace(defaultCoreForAllRules, input.workspace)} + const defaultSelectOptionsForAllRules = workspace + ? {workspace: await createWorkspace(defaultCoreForAllRules, workspace, input.target)} : undefined; - const defaultSelectOptionsForSelectRules = input.workspace - ? {workspace: await createWorkspace(defaultCoreForSelectRules, input.workspace)} + const defaultSelectOptionsForSelectRules = workspace + ? {workspace: await createWorkspace(defaultCoreForSelectRules, workspace, input.target)} : undefined; // EngineProgressListeners should start listening right before we call the Cores' `.selectRules()` methods, since diff --git a/src/lib/actions/RulesAction.ts b/src/lib/actions/RulesAction.ts index b8c00ff8f..07cc38436 100644 --- a/src/lib/actions/RulesAction.ts +++ b/src/lib/actions/RulesAction.ts @@ -24,6 +24,7 @@ export type RulesInput = { 'rule-selector': string[]; 'output-file'?: string[]; workspace?: string[]; + target?: string[]; view?: string; } @@ -51,8 +52,9 @@ export class RulesAction { ...enginePluginModules.map(pluginModule => core.dynamicallyAddEnginePlugin(pluginModule)) ]; await Promise.all(addEnginePromises); - const selectOptions = input.workspace - ? {workspace: await createWorkspace(core, input.workspace)} + const workspace: string[]|undefined = input.workspace || (input.target ? ['.'] : undefined); + const selectOptions = workspace + ? {workspace: await createWorkspace(core, workspace, input.target)} : undefined; // EngineProgressListeners should start listening right before we call Core's `.selectRules()` method, since // that's when progress events can start being emitted. @@ -66,7 +68,7 @@ export class RulesAction { this.dependencies.writer.write(ruleSelection) this.dependencies.viewer.view(rules); - + this.dependencies.actionSummaryViewer.viewPostExecutionSummary( ruleSelection, logWriter.getLogDestination(), diff --git a/src/lib/actions/RunAction.ts b/src/lib/actions/RunAction.ts index a79c9e13c..be8b2a624 100644 --- a/src/lib/actions/RunAction.ts +++ b/src/lib/actions/RunAction.ts @@ -10,7 +10,6 @@ import { } from '@salesforce/code-analyzer-core'; import {CodeAnalyzerConfigFactory} from '../factories/CodeAnalyzerConfigFactory'; import {EnginePluginsFactory} from '../factories/EnginePluginsFactory'; -import {createPathStarts} from '../utils/PathStartUtil'; import {createWorkspace} from '../utils/WorkspaceUtil'; import {ResultsViewer} from '../viewers/ResultsViewer'; import {RunActionSummaryViewer} from '../viewers/ActionSummaryViewer'; @@ -33,9 +32,9 @@ export type RunDependencies = { export type RunInput = { 'config-file'?: string; 'output-file': string[]; - 'path-start'?: string[]; 'rule-selector': string[]; 'severity-threshold'?: SeverityLevel; + target?: string[]; workspace: string[]; } @@ -64,16 +63,13 @@ export class RunAction { ...enginePluginModules.map(pluginModule => core.dynamicallyAddEnginePlugin(pluginModule)) ]; await Promise.all(addEnginePromises); - const workspace: Workspace = await createWorkspace(core, input.workspace); + const workspace: Workspace = await createWorkspace(core, input.workspace, input.target); // EngineProgressListeners should start listening right before we call Core's `.selectRules()` method, since // that's when progress events can start being emitted. this.dependencies.progressListeners.forEach(listener => listener.listen(core)); const ruleSelection: RuleSelection = await core.selectRules(input['rule-selector'], {workspace}); - const runOptions: RunOptions = { - workspace, - pathStartPoints: await createPathStarts(input['path-start']) - }; + const runOptions: RunOptions = {workspace}; const results: RunResults = await core.run(ruleSelection, runOptions); // After Core is done running, the listeners need to be told to stop, since some of them have persistent UI elements // or file handlers that must be gracefully ended. diff --git a/src/lib/factories/EnginePluginsFactory.ts b/src/lib/factories/EnginePluginsFactory.ts index f7d2732d0..7802d009f 100644 --- a/src/lib/factories/EnginePluginsFactory.ts +++ b/src/lib/factories/EnginePluginsFactory.ts @@ -3,7 +3,7 @@ import * as ESLintEngineModule from '@salesforce/code-analyzer-eslint-engine'; import * as PmdCpdEnginesModule from '@salesforce/code-analyzer-pmd-engine'; import * as RetireJSEngineModule from '@salesforce/code-analyzer-retirejs-engine'; import * as RegexEngineModule from '@salesforce/code-analyzer-regex-engine'; -import * as FlowTestEngineModule from '@salesforce/code-analyzer-flowtest-engine'; +import * as FlowEngineModule from '@salesforce/code-analyzer-flow-engine'; import * as SfgeEngineModule from '@salesforce/code-analyzer-sfge-engine'; export interface EnginePluginsFactory { @@ -17,7 +17,7 @@ export class EnginePluginsFactoryImpl implements EnginePluginsFactory { PmdCpdEnginesModule.createEnginePlugin(), RetireJSEngineModule.createEnginePlugin(), RegexEngineModule.createEnginePlugin(), - FlowTestEngineModule.createEnginePlugin(), + FlowEngineModule.createEnginePlugin(), SfgeEngineModule.createEnginePlugin() ]; } diff --git a/src/lib/utils/PathStartUtil.ts b/src/lib/utils/PathStartUtil.ts deleted file mode 100644 index 316e42744..000000000 --- a/src/lib/utils/PathStartUtil.ts +++ /dev/null @@ -1,30 +0,0 @@ -import path from 'node:path'; -import * as fg from 'fast-glob'; -import {getMessage, BundleName} from '../messages'; - -export async function createPathStarts(pathStarts?: string[]): Promise { - if (pathStarts == null) { - return pathStarts; - } - const processedPaths: string[] = []; - for (const pathStart of pathStarts) { - // Negative globs are not currently supported. - if (pathStart.includes("!")) { - throw new Error(getMessage(BundleName.PathStartUtil, 'error.negative-globs-unsupported', [pathStart])); - // If the path a star (*) or question mark in it, we assume it's a glob. - } else if (/[*?]/.test(pathStart)) { - // For the convenience of Windows users, we'll normalize glob paths into UNIX style, so they're valid globs. - const normalizedPathStart = pathStart.replace(/[\\/]/g, '/'); - // Globs and method-level targeting are mutually exclusive. - if (path.basename(pathStart).includes('#')) { - throw new Error(getMessage(BundleName.PathStartUtil, 'error.glob-method-conflict', [pathStart])); - } - // Since Glob results are UNIX-styled, we need to convert the results to use whatever the local path separator - // character is. - processedPaths.push(...(await fg.glob(normalizedPathStart)).map(p => p.replace(/[\\/]/g, path.sep))); - } else { - processedPaths.push(pathStart); - } - } - return processedPaths; -} diff --git a/src/lib/utils/WorkspaceUtil.ts b/src/lib/utils/WorkspaceUtil.ts index 8aa23984e..17da6d211 100644 --- a/src/lib/utils/WorkspaceUtil.ts +++ b/src/lib/utils/WorkspaceUtil.ts @@ -2,23 +2,29 @@ import * as fg from 'fast-glob'; import {CodeAnalyzer, Workspace} from '@salesforce/code-analyzer-core'; import {getMessage, BundleName} from '../messages'; -export async function createWorkspace(core: CodeAnalyzer, workspacePaths: string[]): Promise { +export async function createWorkspace(core: CodeAnalyzer, workspacePaths: string[], targetPaths?: string[]): Promise { + const processedWorkspacePaths: string[] = await processPaths(workspacePaths, 'Workspace'); + const processedTargetPaths: string[]|undefined = targetPaths ? await processPaths(targetPaths, 'Target') : undefined; + // Part of the contract for Core's `createWorkspace()` method is that paths are localized to the current OS, hence + // it's not a problem for Glob results to be UNIX-formatted since they'll be converted right back. + return core.createWorkspace(processedWorkspacePaths, processedTargetPaths); +} + +async function processPaths(rawPaths: string[], pathType: string): Promise { const processedPaths: string[] = []; - for (const workspacePath of workspacePaths) { + for (const rawPath of rawPaths) { // Negative globs are not currently supported. - if (workspacePath.includes("!")) { - throw new Error(getMessage(BundleName.WorkspaceUtil, 'error.negative-globs-unsupported', [workspacePath])); - // If the path has a star (*) or question mark in it, we assume it's a glob. - } else if (/[*?]/.test(workspacePath)) { + if (rawPath.includes("!")) { + throw new Error(getMessage(BundleName.WorkspaceUtil, 'error.negative-globs-unsupported', [pathType, rawPath])); + // If the path has a star (*) or question mark in it, we assume it's a glob. + } else if (/[*?]/.test(rawPath)) { // For the convenience of Windows users, we'll normalize glob paths into UNIX style, so they're valid globs. - const normalizedWorkspacePath = workspacePath.replace(/[\\/]/g, '/'); + const normalizedPath = rawPath.replace(/[\\/]/g, '/'); // NOTE: We're pushing the strict results of the Glob, meaning that the paths will be normalized to UNIX style. - processedPaths.push(...await fg.glob(normalizedWorkspacePath)); + processedPaths.push(...await fg.glob(normalizedPath)); } else { - processedPaths.push(workspacePath); + processedPaths.push(rawPath); } } - // Part of the contract for Core's `createWorkspace()` method is that paths are localized to the current OS, hence - // it's not a problem for Glob results to be UNIX-formatted since they'll be converted right back. - return core.createWorkspace(processedPaths); + return processedPaths; } diff --git a/test/commands/code-analyzer/config.test.ts b/test/commands/code-analyzer/config.test.ts index b6a0d5627..6eb663d3e 100644 --- a/test/commands/code-analyzer/config.test.ts +++ b/test/commands/code-analyzer/config.test.ts @@ -28,184 +28,176 @@ describe('`code-analyzer config` tests', () => { jest.restoreAllMocks(); }); - describe('flags', () => { - describe('--rule-selector', () => { - it('Can be supplied once with a single value', async () => { - const inputValue = 'abcde'; - await ConfigCommand.run(['--rule-selector', inputValue]); - expect(executeSpy).toHaveBeenCalled(); - expect(receivedActionInput).toHaveProperty('rule-selector', [inputValue]); - }); - - it('Can be supplied once with multiple comma-separated values', async () => { - const inputValue = ['abcde', 'defgh']; - await ConfigCommand.run(['--rule-selector', inputValue.join(',')]); - expect(executeSpy).toHaveBeenCalled(); - expect(receivedActionInput).toHaveProperty('rule-selector', inputValue); - }); + describe('--rule-selector', () => { + it('Can be supplied once with a single value', async () => { + const inputValue = 'abcde'; + await ConfigCommand.run(['--rule-selector', inputValue]); + expect(executeSpy).toHaveBeenCalled(); + expect(receivedActionInput).toHaveProperty('rule-selector', [inputValue]); + }); - it('Can be supplied multiple times with one value each', async () => { - const inputValue1 = 'abcde'; - const inputValue2 = 'defgh'; - await ConfigCommand.run(['--rule-selector', inputValue1, '--rule-selector', inputValue2]); - expect(executeSpy).toHaveBeenCalled(); - expect(receivedActionInput).toHaveProperty('rule-selector', [inputValue1, inputValue2]); - }); + it('Can be supplied once with multiple comma-separated values', async () => { + const inputValue = ['abcde', 'defgh']; + await ConfigCommand.run(['--rule-selector', inputValue.join(',')]); + expect(executeSpy).toHaveBeenCalled(); + expect(receivedActionInput).toHaveProperty('rule-selector', inputValue); + }); - it('Can be supplied multiple times with multiple comma-separated values each', async () => { - const inputValue1 = ['abcde', 'hijlk']; - const inputValue2 = ['defgh', 'mnopq']; - await ConfigCommand.run(['--rule-selector', inputValue1.join(','), '--rule-selector', inputValue2.join(',')]); - expect(executeSpy).toHaveBeenCalled(); - expect(receivedActionInput).toHaveProperty('rule-selector', [...inputValue1, ...inputValue2]); - }); + it('Can be supplied multiple times with one value each', async () => { + const inputValue1 = 'abcde'; + const inputValue2 = 'defgh'; + await ConfigCommand.run(['--rule-selector', inputValue1, '--rule-selector', inputValue2]); + expect(executeSpy).toHaveBeenCalled(); + expect(receivedActionInput).toHaveProperty('rule-selector', [inputValue1, inputValue2]); + }); - it('Defaults to value of "all"', async () => { - await ConfigCommand.run([]); - expect(executeSpy).toHaveBeenCalled(); - expect(receivedActionInput).toHaveProperty('rule-selector', ["all"]); - }) - - it('Can be referenced by its shortname, -r', async () => { - const inputValue = 'abcde'; - await ConfigCommand.run(['-r', inputValue]); - expect(executeSpy).toHaveBeenCalled(); - expect(receivedActionInput).toHaveProperty('rule-selector', [inputValue]); - }); + it('Can be supplied multiple times with multiple comma-separated values each', async () => { + const inputValue1 = ['abcde', 'hijlk']; + const inputValue2 = ['defgh', 'mnopq']; + await ConfigCommand.run(['--rule-selector', inputValue1.join(','), '--rule-selector', inputValue2.join(',')]); + expect(executeSpy).toHaveBeenCalled(); + expect(receivedActionInput).toHaveProperty('rule-selector', [...inputValue1, ...inputValue2]); }); - describe('--config-file', () => { - it('Accepts a real file', async () => { - const inputValue = 'package.json'; - await ConfigCommand.run(['--config-file', inputValue]); - expect(executeSpy).toHaveBeenCalled(); - expect(receivedActionInput).toHaveProperty('config-file', inputValue); - }); + it('Defaults to value of "all"', async () => { + await ConfigCommand.run([]); + expect(executeSpy).toHaveBeenCalled(); + expect(receivedActionInput).toHaveProperty('rule-selector', ["all"]); + }) + + it('Can be referenced by its shortname, -r', async () => { + const inputValue = 'abcde'; + await ConfigCommand.run(['-r', inputValue]); + expect(executeSpy).toHaveBeenCalled(); + expect(receivedActionInput).toHaveProperty('rule-selector', [inputValue]); + }); + }); - it('Rejects non-existent file', async () => { - const inputValue = 'definitelyFakeFile.json'; - const executionPromise = ConfigCommand.run(['--config-file', inputValue]); - await expect(executionPromise).rejects.toThrow(`No file found at ${inputValue}`); - expect(executeSpy).not.toHaveBeenCalled(); - }); + describe('--config-file', () => { + it('Accepts a real file', async () => { + const inputValue = 'package.json'; + await ConfigCommand.run(['--config-file', inputValue]); + expect(executeSpy).toHaveBeenCalled(); + expect(receivedActionInput).toHaveProperty('config-file', inputValue); + }); - it('Is unused if not directly specified', async () => { - await ConfigCommand.run([]); - expect(executeSpy).toHaveBeenCalled(); - expect(receivedActionInput['config-file']).toBeUndefined(); - }); + it('Rejects non-existent file', async () => { + const inputValue = 'definitelyFakeFile.json'; + const executionPromise = ConfigCommand.run(['--config-file', inputValue]); + await expect(executionPromise).rejects.toThrow(`No file found at ${inputValue}`); + expect(executeSpy).not.toHaveBeenCalled(); + }); - it('Can only be supplied once', async () => { - const inputValue1 = 'package.json'; - const inputValue2 = 'LICENSE'; - const executionPromise = ConfigCommand.run(['--config-file', inputValue1, '--config-file', inputValue2]); - await expect(executionPromise).rejects.toThrow(`Flag --config-file can only be specified once`); - expect(executeSpy).not.toHaveBeenCalled(); - }); + it('Is unused if not directly specified', async () => { + await ConfigCommand.run([]); + expect(executeSpy).toHaveBeenCalled(); + expect(receivedActionInput['config-file']).toBeUndefined(); + }); - it('Can be referenced by its shortname, -c', async () => { - const inputValue = 'package.json'; - await ConfigCommand.run(['-c', inputValue]); - expect(executeSpy).toHaveBeenCalled(); - expect(receivedActionInput).toHaveProperty('config-file', inputValue); - }); + it('Can only be supplied once', async () => { + const inputValue1 = 'package.json'; + const inputValue2 = 'LICENSE'; + const executionPromise = ConfigCommand.run(['--config-file', inputValue1, '--config-file', inputValue2]); + await expect(executionPromise).rejects.toThrow(`Flag --config-file can only be specified once`); + expect(executeSpy).not.toHaveBeenCalled(); }); - describe('--workspace', () => { - it('Can be supplied once with a single value', async () => { - const inputValue = './somedirectory'; - await ConfigCommand.run(['--workspace', inputValue]); - expect(executeSpy).toHaveBeenCalled(); - expect(receivedActionInput).toHaveProperty('workspace', [inputValue]); - }); + it('Can be referenced by its shortname, -c', async () => { + const inputValue = 'package.json'; + await ConfigCommand.run(['-c', inputValue]); + expect(executeSpy).toHaveBeenCalled(); + expect(receivedActionInput).toHaveProperty('config-file', inputValue); + }); + }); - it('Can be supplied once with multiple comma-separated values', async () => { - const inputValue =['./somedirectory', './someotherdirectory']; - await ConfigCommand.run(['--workspace', inputValue.join(',')]); - expect(executeSpy).toHaveBeenCalled(); - expect(receivedActionInput).toHaveProperty('workspace', inputValue); - }); + describe.each([ + {flag: '--workspace', shortflag: '-w', property: 'workspace'}, + {flag: '--target', shortflag: '-t', property: 'target'} + ])('$flag', ({flag, shortflag, property}) => { + it('Can be supplied once with a single value', async () => { + const inputValue = './somedirectory'; + await ConfigCommand.run([flag, inputValue]); + expect(executeSpy).toHaveBeenCalled(); + expect(receivedActionInput).toHaveProperty(property, [inputValue]); + }); - it('Can be supplied multiple times with one value each', async () => { - const inputValue1 = './somedirectory'; - const inputValue2 = './someotherdirectory'; - await ConfigCommand.run(['--workspace', inputValue1, '--workspace', inputValue2]); - expect(executeSpy).toHaveBeenCalled(); - expect(receivedActionInput).toHaveProperty('workspace', [inputValue1, inputValue2]); - }); + it('Can be supplied once with multiple comma-separated values', async () => { + const inputValue =['./somedirectory', './someotherdirectory']; + await ConfigCommand.run([flag, inputValue.join(',')]); + expect(executeSpy).toHaveBeenCalled(); + expect(receivedActionInput).toHaveProperty(property, inputValue); + }); - it('Can be supplied multiple times with multiple comma-separated values', async () => { - const inputValue1 = ['./somedirectory', './anotherdirectory']; - const inputValue2 = ['./someotherdirectory', './yetanotherdirectory']; - await ConfigCommand.run(['--workspace', inputValue1.join(','), '--workspace', inputValue2.join(',')]); - expect(executeSpy).toHaveBeenCalled(); - expect(receivedActionInput).toHaveProperty('workspace', [...inputValue1, ...inputValue2]); - }); + it('Can be supplied multiple times with one value each', async () => { + const inputValue1 = './somedirectory'; + const inputValue2 = './someotherdirectory'; + await ConfigCommand.run([flag, inputValue1, flag, inputValue2]); + expect(executeSpy).toHaveBeenCalled(); + expect(receivedActionInput).toHaveProperty(property, [inputValue1, inputValue2]); + }); - it('Is unused if not directly specified', async () => { - await ConfigCommand.run([]); - expect(executeSpy).toHaveBeenCalled(); - expect(receivedActionInput.workspace).toBeUndefined(); - }); + it('Is unused if not directly specified', async () => { + await ConfigCommand.run([]); + expect(executeSpy).toHaveBeenCalled(); + expect(receivedActionInput[property]).toBeUndefined(); + }); - it('Can be referenced by its shortname, -w', async () => { - const inputValue = './somedirectory'; - await ConfigCommand.run(['-w', inputValue]); - expect(executeSpy).toHaveBeenCalled(); - expect(receivedActionInput).toHaveProperty('workspace', [inputValue]); - }); + it(`Can be referenced by its shortname, ${shortflag}`, async () => { + const inputValue = './somedirectory'; + await ConfigCommand.run([shortflag, inputValue]); + expect(executeSpy).toHaveBeenCalled(); + expect(receivedActionInput).toHaveProperty(property, [inputValue]); }); + }); - describe('--output-file', () => { + describe('--output-file', () => { - let fromFileSpy: jest.SpyInstance; - let receivedFile: string|null; + let fromFileSpy: jest.SpyInstance; + let receivedFile: string|null; - beforeEach(() => { - const originalFromFile = ConfigFileWriter.fromFile; - fromFileSpy = jest.spyOn(ConfigFileWriter, 'fromFile').mockImplementation(file => { - receivedFile = file; - return originalFromFile(file, new SpyDisplay()); - }); + beforeEach(() => { + const originalFromFile = ConfigFileWriter.fromFile; + fromFileSpy = jest.spyOn(ConfigFileWriter, 'fromFile').mockImplementation(file => { + receivedFile = file; + return originalFromFile(file, new SpyDisplay()); }); + }); - it('Can be supplied once with a single value', async () => { - const inputValue = './somefile.yml'; - await ConfigCommand.run(['--output-file', inputValue]); - expect(executeSpy).toHaveBeenCalled(); - expect(createActionSpy).toHaveBeenCalled(); - expect(fromFileSpy).toHaveBeenCalled(); - expect(receivedFile).toEqual(inputValue); - expect(receivedActionInput).toHaveProperty('output-file', inputValue); - }); - - it('Can be referenced by its shortname, -f', async () => { - const inputValue = './somefile.yml'; - await ConfigCommand.run(['-f', inputValue]); - expect(executeSpy).toHaveBeenCalled(); - expect(createActionSpy).toHaveBeenCalled(); - expect(fromFileSpy).toHaveBeenCalled(); - expect(receivedFile).toEqual(inputValue); - expect(receivedActionInput).toHaveProperty('output-file', inputValue); - }); + it('Can be supplied once with a single value', async () => { + const inputValue = './somefile.yml'; + await ConfigCommand.run(['--output-file', inputValue]); + expect(executeSpy).toHaveBeenCalled(); + expect(createActionSpy).toHaveBeenCalled(); + expect(fromFileSpy).toHaveBeenCalled(); + expect(receivedFile).toEqual(inputValue); + expect(receivedActionInput).toHaveProperty('output-file', inputValue); + }); - it('Cannot be supplied multiple times', async () => { - const inputValue1 = './somefile.yml'; - const inputValue2 = './someotherfile.yml'; - const executionPromise = ConfigCommand.run(['--output-file', inputValue1, '--output-file', inputValue2]); - await expect(executionPromise).rejects.toThrow(`Flag --output-file can only be specified once`); - expect(executeSpy).not.toHaveBeenCalled(); - }); + it('Can be referenced by its shortname, -f', async () => { + const inputValue = './somefile.yml'; + await ConfigCommand.run(['-f', inputValue]); + expect(executeSpy).toHaveBeenCalled(); + expect(createActionSpy).toHaveBeenCalled(); + expect(fromFileSpy).toHaveBeenCalled(); + expect(receivedFile).toEqual(inputValue); + expect(receivedActionInput).toHaveProperty('output-file', inputValue); + }); - it('Is unused if not directly specified', async () => { - await ConfigCommand.run([]); - expect(executeSpy).toHaveBeenCalled(); - expect(createActionSpy).toHaveBeenCalled(); - expect(fromFileSpy).not.toHaveBeenCalled(); - expect(receivedActionInput['output-file']).toBeUndefined(); - }); + it('Cannot be supplied multiple times', async () => { + const inputValue1 = './somefile.yml'; + const inputValue2 = './someotherfile.yml'; + const executionPromise = ConfigCommand.run(['--output-file', inputValue1, '--output-file', inputValue2]); + await expect(executionPromise).rejects.toThrow(`Flag --output-file can only be specified once`); + expect(executeSpy).not.toHaveBeenCalled(); + }); + it('Is unused if not directly specified', async () => { + await ConfigCommand.run([]); + expect(executeSpy).toHaveBeenCalled(); + expect(createActionSpy).toHaveBeenCalled(); + expect(fromFileSpy).not.toHaveBeenCalled(); + expect(receivedActionInput['output-file']).toBeUndefined(); }); }); }); diff --git a/test/commands/code-analyzer/rules.test.ts b/test/commands/code-analyzer/rules.test.ts index 4643739b0..95ebc5d5d 100644 --- a/test/commands/code-analyzer/rules.test.ts +++ b/test/commands/code-analyzer/rules.test.ts @@ -128,13 +128,13 @@ describe('`code-analyzer rules` tests', () => { expect(fromFilesSpy).toHaveBeenCalled(); expect(receivedFiles).toEqual([inputValue1]); }); - + it('Can only be supplied once', async () => { const executionPromise = RulesCommand.run(['--output-file', inputValue1, '--output-file', inputValue2]); await expect(executionPromise).rejects.toThrow(`Flag --output-file can only be specified once`); expect(executeSpy).not.toHaveBeenCalled(); }); - + it('Can be referenced by its shortname, -f', async () => { await RulesCommand.run(['-f', inputValue1]); expect(executeSpy).toHaveBeenCalled(); @@ -150,7 +150,7 @@ describe('`code-analyzer rules` tests', () => { expect(receivedFiles).toEqual([]); }); }); - + describe('--view', () => { it('Accepts the value, "table"', async () => { const inputValue = 'table'; @@ -200,35 +200,38 @@ describe('`code-analyzer rules` tests', () => { }); }); - describe('--workspace', () => { + describe.each([ + {flag: '--workspace', shortflag: '-w', property: 'workspace'}, + {flag: '--target', shortflag: '-t', property: 'target'}, + ])('$flag', ({flag, shortflag, property}) => { it('Can be supplied once with a single value', async () => { const inputValue = './somedirectory'; - await RulesCommand.run(['--workspace', inputValue]); + await RulesCommand.run([flag, inputValue]); expect(executeSpy).toHaveBeenCalled(); - expect(receivedActionInput).toHaveProperty('workspace', [inputValue]); + expect(receivedActionInput).toHaveProperty(property, [inputValue]); }); it('Can be supplied once with multiple comma-separated values', async () => { const inputValue =['./somedirectory', './someotherdirectory']; - await RulesCommand.run(['--workspace', inputValue.join(',')]); + await RulesCommand.run([flag, inputValue.join(',')]); expect(executeSpy).toHaveBeenCalled(); - expect(receivedActionInput).toHaveProperty('workspace', inputValue); + expect(receivedActionInput).toHaveProperty(property, inputValue); }); it('Can be supplied multiple times with one value each', async () => { const inputValue1 = './somedirectory'; const inputValue2 = './someotherdirectory'; - await RulesCommand.run(['--workspace', inputValue1, '--workspace', inputValue2]); + await RulesCommand.run([flag, inputValue1, flag, inputValue2]); expect(executeSpy).toHaveBeenCalled(); - expect(receivedActionInput).toHaveProperty('workspace', [inputValue1, inputValue2]); + expect(receivedActionInput).toHaveProperty(property, [inputValue1, inputValue2]); }); it('Can be supplied multiple times with multiple comma-separated values', async () => { const inputValue1 = ['./somedirectory', './anotherdirectory']; const inputValue2 = ['./someotherdirectory', './yetanotherdirectory']; - await RulesCommand.run(['--workspace', inputValue1.join(','), '--workspace', inputValue2.join(',')]); + await RulesCommand.run([flag, inputValue1.join(','), flag, inputValue2.join(',')]); expect(executeSpy).toHaveBeenCalled(); - expect(receivedActionInput).toHaveProperty('workspace', [...inputValue1, ...inputValue2]); + expect(receivedActionInput).toHaveProperty(property, [...inputValue1, ...inputValue2]); }); it('Is unused if not directly specified', async () => { @@ -237,11 +240,11 @@ describe('`code-analyzer rules` tests', () => { expect(receivedActionInput.workspace).toBeUndefined(); }); - it('Can be referenced by its shortname, -w', async () => { + it(`Can be referenced by its shortname, ${shortflag}`, async () => { const inputValue = './somedirectory'; - await RulesCommand.run(['-w', inputValue]); + await RulesCommand.run([shortflag, inputValue]); expect(executeSpy).toHaveBeenCalled(); - expect(receivedActionInput).toHaveProperty('workspace', [inputValue]); + expect(receivedActionInput).toHaveProperty(property, [inputValue]); }); }); diff --git a/test/commands/code-analyzer/run.test.ts b/test/commands/code-analyzer/run.test.ts index 74bb6c6ad..b672905b7 100644 --- a/test/commands/code-analyzer/run.test.ts +++ b/test/commands/code-analyzer/run.test.ts @@ -80,42 +80,42 @@ describe('`code-analyzer run` tests', () => { }); }); - describe('--path-start', () => { + describe('--target', () => { it('Can be supplied once with a single value', async () => { const inputValue = './somefile.cls'; - await RunCommand.run(['--path-start', inputValue]); + await RunCommand.run(['--target', inputValue]); expect(executeSpy).toHaveBeenCalled(); - expect(receivedActionInput).toHaveProperty('path-start', [inputValue]); + expect(receivedActionInput).toHaveProperty('target', [inputValue]); }); it('Can be supplied once with multiple comma-separated values', async () => { const inputValue =['./somefile.cls', './someotherfile.cls']; - await RunCommand.run(['--path-start', inputValue.join(',')]); + await RunCommand.run(['--target', inputValue.join(',')]); expect(executeSpy).toHaveBeenCalled(); - expect(receivedActionInput).toHaveProperty('path-start', inputValue); + expect(receivedActionInput).toHaveProperty('target', inputValue); }); it('Can be supplied multiple times with one value each', async () => { const inputValue1 = './somefile.cls'; const inputValue2 = './someotherfile.cls'; - await RunCommand.run(['--path-start', inputValue1, '--path-start', inputValue2]); + await RunCommand.run(['--target', inputValue1, '--target', inputValue2]); expect(executeSpy).toHaveBeenCalled(); - expect(receivedActionInput).toHaveProperty('path-start', [inputValue1, inputValue2]); + expect(receivedActionInput).toHaveProperty('target', [inputValue1, inputValue2]); }); it('Can be supplied multiple times with multiple comma-separated values', async () => { const inputValue1 = ['./somefile.cls', './anotherfile.cls']; const inputValue2 = ['./someotherfile.cls', './yetanotherfile.cls']; - await RunCommand.run(['--path-start', inputValue1.join(','), '--path-start', inputValue2.join(',')]); + await RunCommand.run(['--target', inputValue1.join(','), '--target', inputValue2.join(',')]); expect(executeSpy).toHaveBeenCalled(); - expect(receivedActionInput).toHaveProperty('path-start', [...inputValue1, ...inputValue2]); + expect(receivedActionInput).toHaveProperty('target', [...inputValue1, ...inputValue2]); }); - it('Can be referenced by its shortname, -s', async () => { + it('Can be referenced by its shortname, -t', async () => { const inputValue = './somefile.cls'; - await RunCommand.run(['-s', inputValue]); + await RunCommand.run(['-t', inputValue]); expect(executeSpy).toHaveBeenCalled(); - expect(receivedActionInput).toHaveProperty('path-start', [inputValue]); + expect(receivedActionInput).toHaveProperty('target', [inputValue]); }); }); @@ -198,9 +198,9 @@ describe('`code-analyzer run` tests', () => { expect(receivedActionInput['severity-threshold']).toBeUndefined(); }); - it('Can be referenced by its shortname, -t', async () => { + it('Can be referenced by its shortname, -s', async () => { const inputValue = `3` - await RunCommand.run(['-t', inputValue]); + await RunCommand.run(['-s', inputValue]); expect(executeSpy).toHaveBeenCalled(); expect(receivedActionInput).toHaveProperty('severity-threshold', parseInt(inputValue)); }); diff --git a/test/fixtures/comparison-files/lib/actions/ConfigAction.test.ts/workspace-resolution/workspaceAwareRules.yml.goldfile b/test/fixtures/comparison-files/lib/actions/ConfigAction.test.ts/workspace-resolution/workspaceAwareRules.yml.goldfile new file mode 100644 index 000000000..ed925b827 --- /dev/null +++ b/test/fixtures/comparison-files/lib/actions/ConfigAction.test.ts/workspace-resolution/workspaceAwareRules.yml.goldfile @@ -0,0 +1,14 @@ +rules: + + # ====================================================================== + # WORKSPACEAWAREENGINE ENGINE RULE OVERRIDES + # ====================================================================== + workspaceAwareEngine: + "ruleForREADME.md": + severity: 4 + tags: + - Recommended + "ruleForpackage.json": + severity: 4 + tags: + - Recommended \ No newline at end of file diff --git a/test/lib/actions/ConfigAction.test.ts b/test/lib/actions/ConfigAction.test.ts index d34f902f4..f17ed92a9 100644 --- a/test/lib/actions/ConfigAction.test.ts +++ b/test/lib/actions/ConfigAction.test.ts @@ -398,6 +398,55 @@ describe('ConfigAction tests', () => { }); }); + describe('Target/Workspace resolution', () => { + const originalCwd: string = process.cwd(); + const baseDir: string = path.resolve(__dirname, '..', '..', '..'); + + beforeEach(() => { + process.chdir(baseDir); + }); + + afterEach(() => { + process.chdir(originalCwd); + }); + + it.each([ + { + case: 'a workspace is applied to the config', + workspace: [path.join(baseDir, 'package.json'), path.join(baseDir, 'README.md')], + target: undefined + }, + { + case: 'a target further narrows the explicitly defined workspace', + workspace: ['.'], + target: ['package.json', 'README.md'] + }, + { + case: 'a target further narrows an implicitly defined workspace', + workspace: undefined, + target: ['package.json', 'README.md'] + } + ])('When $case, only the relevant rules are returned', async ({workspace, target}) => { + // ==== SETUP ==== + spyDisplay = new SpyDisplay(); + dependencies = { + logEventListeners: [new LogEventDisplayer(spyDisplay)], + progressEventListeners: [], + viewer: new ConfigStyledYamlViewer(spyDisplay), + configFactory: new StubCodeAnalyzerConfigFactory(), + actionSummaryViewer: new ConfigActionSummaryViewer(spyDisplay), + pluginsFactory: new WorkspaceAwareEnginePluginFactory() + }; + + // ==== TESTED BEHAVIOR ==== + const output: string = await runActionAndGetDisplayedConfig(dependencies, ['all'], undefined, workspace, target); + + // ==== ASSERTIONS ==== + const goldFileContents: string = await readGoldFile(path.join(PATH_TO_COMPARISON_DIR, 'workspace-resolution', 'workspaceAwareRules.yml.goldfile')); + expect(output).toContain(goldFileContents); + }); + }); + describe('File Creation', () => { beforeEach(() => { spyDisplay = new SpyDisplay(); @@ -522,12 +571,14 @@ describe('ConfigAction tests', () => { return fsp.readFile(goldFilePath, {encoding: 'utf-8'}); } - async function runActionAndGetDisplayedConfig(dependencies: ConfigDependencies, ruleSelectors: string[], configFile?: string): Promise { + async function runActionAndGetDisplayedConfig(dependencies: ConfigDependencies, ruleSelectors: string[], configFile?: string, workspace?: string[], target?: string[]): Promise { // ==== SETUP ==== const action = ConfigAction.createAction(dependencies); const input: ConfigInput = { 'rule-selector': ruleSelectors, - 'config-file': configFile + 'config-file': configFile, + workspace, + target }; // ==== TESTED BEHAVIOR ==== @@ -819,6 +870,71 @@ class StubEngine3 extends EngineApi.Engine { } } +class WorkspaceAwareEnginePluginFactory implements EnginePluginsFactory { + public create(): EngineApi.EnginePlugin[] { + return [new WorkspaceAwareEnginePlugin()]; + } +} + +class WorkspaceAwareEnginePlugin extends EngineApi.EnginePluginV1 { + private readonly createdEngines: Map = new Map(); + + getAvailableEngineNames(): string[] { + return ['workspaceAwareEngine']; + } + + createEngine(engineName: string, config: EngineApi.ConfigObject): Promise { + if (engineName === 'workspaceAwareEngine') { + this.createdEngines.set(engineName, new WorkspaceAwareEngine(config)); + } else { + throw new Error(`no engine named ${engineName}`); + } + return Promise.resolve(this.getCreatedEngine(engineName)); + } + + public getCreatedEngine(engineName: string): EngineApi.Engine { + if (this.createdEngines.has(engineName)) { + return this.createdEngines.get(engineName)!; + } + throw new Error(`Engine named ${engineName} not yet instantiated`); + } +} + +class WorkspaceAwareEngine extends EngineApi.Engine { + public constructor(_config: EngineApi.ConfigObject) { + super(); + } + + public getName(): string { + return 'workspaceAwareEngine'; + } + + public getEngineVersion(): Promise { + return Promise.resolve('1.0.0'); + } + + public async describeRules(describeOptions: EngineApi.DescribeOptions): Promise { + if (!describeOptions.workspace) { + return Promise.resolve([]); + } + + // Derive a rule for each of the targeted files. + return (await describeOptions.workspace.getTargetedFiles()).map(fileOrFolder => { + return { + name: `ruleFor${path.basename(fileOrFolder)}`, + severityLevel: EngineApi.SeverityLevel.Low, + tags: ['Recommended'], + description: `Rule synthesized for target "${fileOrFolder}`, + resourceUrls: [`https://example.com/${fileOrFolder}`] + }; + }); + } + + public runRules(_ruleNames: string[], _runOptions: EngineApi.RunOptions): Promise { + // Don't need to actually run any rules, since we're just testing configuration. + return Promise.resolve({violations: []}); + } +} class FactoryForThrowingEnginePlugin implements EnginePluginsFactory { create(): EngineApi.EnginePlugin[] { diff --git a/test/lib/actions/RulesAction.test.ts b/test/lib/actions/RulesAction.test.ts index 11243f1c9..c0f8730b0 100644 --- a/test/lib/actions/RulesAction.test.ts +++ b/test/lib/actions/RulesAction.test.ts @@ -93,35 +93,65 @@ describe('RulesAction tests', () => { expect(writerCallHistory).toHaveLength(1); }); - it('Engines with target-dependent rules return the right rules', async () => { - const dependencies: RulesDependencies = { - configFactory: new StubDefaultConfigFactory(), - // The engine we're using here will synthesize one rule per target. - pluginsFactory: new StubEnginePluginFactories.StubEnginePluginsFactory_withTargetDependentStubEngine(), - logEventListeners: [], - progressListeners: [], - actionSummaryViewer, - viewer, - writer - }; - const targetedFilesAndFolders = ['package.json', 'src', 'README.md']; - const action = RulesAction.createAction(dependencies); - const input: RulesInput = { - 'rule-selector': ['Recommended'], - workspace: targetedFilesAndFolders - }; + describe('Target/Workspace resolution', () => { + const originalCwd: string = process.cwd(); + const baseDir: string = path.resolve(__dirname, '..', '..', '..'); - await action.execute(input); + beforeEach(() => { + process.chdir(baseDir); + }); - const viewerCallHistory = viewer.getCallHistory(); - expect(viewerCallHistory).toHaveLength(1); - const expectedRuleNames = targetedFilesAndFolders.map(t => `ruleFor${path.resolve(t)}`); - const actualRuleNames = viewerCallHistory[0].map(rule => rule.getName()); - expect(actualRuleNames).toHaveLength(expectedRuleNames.length); - // The rules' order might not exactly match the provided targets', but as long as they're all present, it's fine. - for (const expectedRuleName of expectedRuleNames) { - expect(actualRuleNames).toContain(expectedRuleName); - } + afterEach(() => { + process.chdir(originalCwd); + }) + + it.each([ + { + case: 'a workspace narrows the applicable files', + workspace: [path.join(baseDir, 'package.json'), path.join(baseDir, 'README.md')], + target: undefined + }, + { + case: 'a target further narrows an explicitly defined workspace', + workspace: ['.'], + target: ['package.json', 'README.md'] + }, + { + case: 'a target further narrows an implicitly defined workspace', + workspace: undefined, + target: ['package.json', 'README.md'] + } + ])('When $case, only the relevant rules are returned', async ({workspace, target}) => { + const dependencies: RulesDependencies = { + configFactory: new StubDefaultConfigFactory(), + // The engine we're using here will synthesize one rule per target. + pluginsFactory: new StubEnginePluginFactories.StubEnginePluginsFactory_withTargetDependentStubEngine(), + logEventListeners: [], + progressListeners: [], + actionSummaryViewer, + viewer, + writer + }; + const action = RulesAction.createAction(dependencies); + const input: RulesInput = { + 'rule-selector': ['Recommended'], + workspace, + target + }; + + await action.execute(input); + + const viewerCallHistory = viewer.getCallHistory(); + expect(viewerCallHistory).toHaveLength(1); + const expectedFilesAndFolders = ['package.json', 'README.md']; + const expectedRuleNames = expectedFilesAndFolders.map(t => `ruleFor${path.resolve(t)}`); + const actualRuleNames = viewerCallHistory[0].map(rule => rule.getName()); + expect(actualRuleNames).toHaveLength(expectedRuleNames.length); + // The rules' order might not exactly match the provided targets', but as long as they're all present, it's fine. + for (const expectedRuleName of expectedRuleNames) { + expect(actualRuleNames).toContain(expectedRuleName); + } + }); }); /** @@ -221,9 +251,9 @@ describe('RulesAction tests', () => { 'rule-selector': ['Codestyle'], 'output-file': [outfilePath] }; - + await action.execute(input); - + const preExecutionGoldfileContents: string = await fsp.readFile(preExecutionGoldfilePath, 'utf-8'); const goldfileContents: string = (await fsp.readFile(summaryGoldfilePath, 'utf-8')) .replace(`{{PATH_TO_FILE}}`, outfilePath); diff --git a/test/lib/actions/RunAction.test.ts b/test/lib/actions/RunAction.test.ts index 69e6ad195..6f94acbc4 100644 --- a/test/lib/actions/RunAction.test.ts +++ b/test/lib/actions/RunAction.test.ts @@ -93,7 +93,7 @@ describe('RunAction tests', () => { // Verify that the expected rules were executed on the right files. const actualExecutedRules = engine1.runRulesCallHistory[0].ruleNames; expect(actualExecutedRules).toEqual(expectedRules); - const actualTargetFiles = engine1.runRulesCallHistory[0].runOptions.workspace.getFilesAndFolders(); + const actualTargetFiles = engine1.runRulesCallHistory[0].runOptions.workspace.getRawFilesAndFolders(); expect(actualTargetFiles).toEqual([path.resolve('.')]); // Verify that the expected results were passed into the Viewer and Writer. expect(writer.getCallHistory()[0].getViolationCount()).toEqual(1); @@ -102,18 +102,28 @@ describe('RunAction tests', () => { expect(resultsViewer.getCallHistory()[0].getViolations()[0].getMessage()).toEqual('Fake message'); }); - it('Engines with target-dependent rules run the right rules', async () => { + it.each([ + { + case: 'Workspace only', + workspace: ['package.json', 'README.md'], + target: undefined + }, + { + case: 'Workspace and Targets', + workspace: ['.'], + target: ['package.json', 'README.md'] + } + ])('Engines with target-dependent rules run the right rules. Case: $case', async ({workspace, target}) => { // ==== SETUP ==== // Add a target-dependent engine to the engines that will be run. const targetDependentEngine: TargetDependentEngine1 = new TargetDependentEngine1({}); stubEnginePlugin.addEngine(targetDependentEngine); - // Select a few specific targets instead of vacuously selecting the whole project. - const targetedFilesAndFolders = ['package.json', 'src', 'README.md']; // Create the input const input: RunInput = { // Select only rules in the target-dependent engine. "rule-selector": [targetDependentEngine.getName()], - "workspace": targetedFilesAndFolders, + workspace, + target, 'output-file': [] }; @@ -125,8 +135,9 @@ describe('RunAction tests', () => { expect(engine1.runRulesCallHistory).toHaveLength(0); const actualExecutedRules = targetDependentEngine.runRulesCallHistory[0].ruleNames; // One rule per target should have been run in the target-dependent engine. - expect(actualExecutedRules).toHaveLength(targetedFilesAndFolders.length); - const expectedRuleNames = targetedFilesAndFolders.map(t => `ruleFor${path.resolve(t)}`); + const expectedTargetFiles = ['package.json', 'README.md']; + expect(actualExecutedRules).toHaveLength(expectedTargetFiles.length); + const expectedRuleNames = expectedTargetFiles.map(t => `ruleFor${path.resolve(t)}`); // The rules' order might not exactly match the provided targets', but as long as they're all present, it's fine. for (const expectedRuleName of expectedRuleNames) { expect(actualExecutedRules).toContain(expectedRuleName); @@ -287,7 +298,7 @@ describe('RunAction tests', () => { // Verify that the expected rules were executed on the right files. const actualExecutedRules = engine1.runRulesCallHistory[0].ruleNames; expect(actualExecutedRules).toEqual(expectedRules); - const actualTargetFiles = engine1.runRulesCallHistory[0].runOptions.workspace.getFilesAndFolders(); + const actualTargetFiles = engine1.runRulesCallHistory[0].runOptions.workspace.getRawFilesAndFolders(); expect(actualTargetFiles).toEqual([path.resolve('.')]); // Verify that the summary output matches the expectation. const preExecutionGoldfileContents: string = await fsp.readFile(path.join(PATH_TO_GOLDFILES, 'action-summaries', 'pre-execution-summary.txt.goldfile'), 'utf-8'); @@ -336,7 +347,7 @@ describe('RunAction tests', () => { // Verify that the expected rules were executed on the right files. const actualExecutedRules = engine1.runRulesCallHistory[0].ruleNames; expect(actualExecutedRules).toEqual(expectedRules); - const actualTargetFiles = engine1.runRulesCallHistory[0].runOptions.workspace.getFilesAndFolders(); + const actualTargetFiles = engine1.runRulesCallHistory[0].runOptions.workspace.getRawFilesAndFolders(); expect(actualTargetFiles).toEqual([path.resolve('.')]); // Verify that the summary output matches the expectation. const preExecutionGoldfileContents: string = await fsp.readFile(path.join(PATH_TO_GOLDFILES, 'action-summaries', 'pre-execution-summary.txt.goldfile'), 'utf-8'); diff --git a/test/lib/factories/EnginePluginsFactory.test.ts b/test/lib/factories/EnginePluginsFactory.test.ts index b7d7c8c23..4b10d9451 100644 --- a/test/lib/factories/EnginePluginsFactory.test.ts +++ b/test/lib/factories/EnginePluginsFactory.test.ts @@ -11,7 +11,7 @@ describe('EnginePluginsFactoryImpl', () => { expect(enginePlugins[1].getAvailableEngineNames()).toEqual(['pmd', 'cpd']); expect(enginePlugins[2].getAvailableEngineNames()).toEqual(['retire-js']); expect(enginePlugins[3].getAvailableEngineNames()).toEqual(['regex']); - expect(enginePlugins[4].getAvailableEngineNames()).toEqual(['flowtest']); + expect(enginePlugins[4].getAvailableEngineNames()).toEqual(['flow']); expect(enginePlugins[5].getAvailableEngineNames()).toEqual(['sfge']); }); }); diff --git a/test/lib/utils/PathStartUtil.test.ts b/test/lib/utils/PathStartUtil.test.ts deleted file mode 100644 index 3887fb94d..000000000 --- a/test/lib/utils/PathStartUtil.test.ts +++ /dev/null @@ -1,223 +0,0 @@ -import path from 'node:path'; -import * as PathStartUtil from '../../../src/lib/utils/PathStartUtil'; - - -describe('PathStartUtil', () => { - describe('#createPathStarts()', () => { - const ORIGINAL_TEST_DIR = process.cwd(); - const PATH_TO_WORKSPACE = path.join(__dirname, '..', '..', 'fixtures', 'example-workspaces', 'workspace-with-dotted-items'); - - beforeEach(() => { - process.chdir(PATH_TO_WORKSPACE); - }); - - afterEach(() => { - process.chdir(ORIGINAL_TEST_DIR); - }) - - it('When passed undefined, returns undefined', async () => { - const output = await PathStartUtil.createPathStarts(undefined); - expect(output).toBeUndefined(); - }); - - it('When passed an empty array, returns an empty array', async () => { - const output = await PathStartUtil.createPathStarts([]); - expect(output).toEqual([]); - }); - - describe('File matching', () => { - it.each([ - {scenario: 'a single relative file', expectation: 'just that file', input: ['undotted-file-1.txt']}, - { - scenario: 'a list of relative files', - expectation: 'just those files', - input: [path.join('.', 'undotted-directory-a', 'undotted-file-2a.txt'), '.dotted-file-2.txt'] - }, - { - scenario: 'a single absolute file', - expectation: 'just that file', - input: [path.join(PATH_TO_WORKSPACE, 'undotted-file-1.txt')] - }, - { - scenario: 'a list of absolute files', - expectation: 'just those files', - input: [ - path.join(PATH_TO_WORKSPACE, 'undotted-directory-a', 'undotted-file-2a.txt'), - path.join(PATH_TO_WORKSPACE, 'undotted-file-2.txt') - ] - } - ])('Given $scenario, returns $expectation', async ({input}) => { - const output = await PathStartUtil.createPathStarts(input); - expect(output).toBeDefined(); - expect(output as string[]).toHaveLength(input.length); - expect(output as string[]).toEqual(input); - }); - - it('File names with brackets are matched as files instead of globs', async () => { - const input = [path.join('.', 'path-name[with]brackets.txt')]; - const output = await PathStartUtil.createPathStarts(input); - expect(output).toBeDefined(); - expect(output as string[]).toHaveLength(input.length); - expect(output as string[]).toEqual(input); - }); - }); - - describe('Glob matching', () => { - it.each([ - { - scenario: 'a single relative file', - glob: path.join('.', 'undotted*1.txt'), - expectation: new Set(['undotted-file-1.txt']) - }, - { - scenario: 'a list of relative files', - glob: path.join('.', 'undotted*'), - expectation: new Set(['undotted-file-1.txt', 'undotted-file-2.txt']) - }, - { - scenario: 'a single absolute file', - glob: path.resolve(PATH_TO_WORKSPACE, 'undotted*1.txt'), - expectation: new Set([ - path.resolve(PATH_TO_WORKSPACE, 'undotted-file-1.txt') - ]) - }, - { - scenario: 'a list of absolute files', - glob: path.resolve(PATH_TO_WORKSPACE, 'undotted*'), - expectation: new Set([ - path.resolve(PATH_TO_WORKSPACE, 'undotted-file-1.txt'), - path.resolve(PATH_TO_WORKSPACE, 'undotted-file-2.txt') - ]) - } - ])('Given a glob that matches $scenario, returns the correct files', async ({glob, expectation}) => { - const output = await PathStartUtil.createPathStarts([glob]); - expect(output).toBeDefined(); - expect(output as string[]).toHaveLength(expectation.size); - for (const expectedPathStart of expectation.keys()) { - expect(output as string[]).toContain(expectedPathStart); - } - }); - - describe('Dotted file handling', () => { - it.each([ - { - scenario: 'the current directory', - glob: path.join('.', '.*'), - expectation: new Set([ - '.dotted-file-1.txt', - '.dotted-file-2.txt' - ]) - }, - { - scenario: 'dotted subfolders of the current directory', - glob: path.join('.', '.*', '.*'), - expectation: new Set([ - path.join('.dotted-directory-a', '.dotted-file-1a.txt'), - path.join('.dotted-directory-b', '.dotted-file-1b.txt') - ]) - }, - { - scenario: 'undotted subfolders of the current directory', - glob: path.join('.', '*', '.*'), - expectation: new Set([ - path.join('undotted-directory-a', '.dotted-file-2a.txt'), - path.join('undotted-directory-b', '.dotted-file-2b.txt') - ]) - } - ])('A glob explicitly seeking dotted files in $scenario will find them', async ({ - glob, - expectation - }) => { - const output = await PathStartUtil.createPathStarts([glob]); - expect(output).toBeDefined(); - expect(output as string[]).toHaveLength(expectation.size); - for (const expectedPathStart of expectation.keys()) { - expect(output as string[]).toContain(expectedPathStart); - } - }); - - it.each([ - { - scenario: 'the current directory', - glob: path.join('.', '*'), - expectation: new Set([ - 'undotted-file-1.txt', - 'undotted-file-2.txt' - ]) - }, - { - scenario: 'dotted subfolders of the current directory', - glob: path.join('.', '.**', '*'), - expectation: new Set([ - path.join('.dotted-directory-a', 'undotted-file-1a.txt'), - path.join('.dotted-directory-b', 'undotted-file-1b.txt') - ]) - }, - { - scenario: 'undotted subfolders of the current directory', - glob: path.join('.', '*', '*'), - expectation: new Set([ - path.join('undotted-directory-a', 'undotted-file-2a.txt'), - path.join('undotted-directory-b', 'undotted-file-2b.txt') - ]) - }, - ])('A glob NOT explicitly seeking dotted items in $scenario will find only undotted items', async ({ - glob, - expectation - }) => { - const output = await PathStartUtil.createPathStarts([glob]); - expect(output).toBeDefined(); - expect(output as string[]).toHaveLength(expectation.size); - for (const expectedPathStart of expectation.keys()) { - expect(output as string[]).toContain(expectedPathStart); - } - }) - }) - }); - - describe('Method matching', () => { - it('When given a method in a file, returns that method and file', async () => { - const input = path.join('.', 'undotted-file-1.txt#someMethod'); - const output = await PathStartUtil.createPathStarts([input]); - expect(output).toBeDefined(); - expect(output as string[]).toHaveLength(1); - expect((output as string[])[0]).toMatch('undotted-file-1.txt#someMethod'); - }); - }); - - describe('Pathological cases', () => { - it('Negative globs are not supported', async () => { - const input = path.join('!', '.', '**', '*.cls'); - - // Typically we'd use Jest's `expect().toThrow()` method, but since we need to assert specific things about - // the error, we're doing this instead. - let thrownError: Error | null = null; - try { - await PathStartUtil.createPathStarts([input]); - } catch (e) { - thrownError = e; - } - expect(thrownError).toBeInstanceOf(Error); - // Expect the error message to mention globs in some capacity. - expect((thrownError as Error).message).toContain('glob'); - }); - - it('Method matching and globs are mutually exclusive', async () => { - const input = path.join('.', '**', '*.cls#someMethod'); - - // Typically we'd use Jest's `expect().toThrow()` method, but since we need to assert specific things about - // the error, we're doing this instead. - let thrownError: Error | null = null; - try { - await PathStartUtil.createPathStarts([input]); - } catch (e) { - thrownError = e; - } - expect(thrownError).toBeInstanceOf(Error); - // Expect the error message to mention globs in some capacity. - expect((thrownError as Error).message).toContain('glob'); - }); - }); - }); -}) - diff --git a/test/lib/utils/WorkspaceUtil.test.ts b/test/lib/utils/WorkspaceUtil.test.ts index 9c5e883f7..b5ca67867 100644 --- a/test/lib/utils/WorkspaceUtil.test.ts +++ b/test/lib/utils/WorkspaceUtil.test.ts @@ -82,7 +82,7 @@ describe('WorkspaceUtil', () => { const workspace: Workspace = await WorkspaceUtil.createWorkspace(core, input); // ==== ASSERTIONS ==== - const workspaceItems: string[] = workspace.getFilesAndFolders(); + const workspaceItems: string[] = workspace.getRawFilesAndFolders(); const expectedWorkspaceSet = new Set(input.map(i => path.resolve('.', i))); expect(workspaceItems).toHaveLength(expectedWorkspaceSet.size); for (const expectedWorkspaceItem of expectedWorkspaceSet.keys()) { @@ -135,7 +135,7 @@ describe('WorkspaceUtil', () => { const workspace: Workspace = await WorkspaceUtil.createWorkspace(core, [input]); // ==== ASSERTIONS ==== - const actualWorkspacePaths: string[] = workspace.getFilesAndFolders(); + const actualWorkspacePaths: string[] = workspace.getRawFilesAndFolders(); expect(actualWorkspacePaths).toHaveLength(output.size); for (const expectedWorkspacePath of output.keys()) { expect(actualWorkspacePaths).toContain(expectedWorkspacePath); @@ -180,7 +180,7 @@ describe('WorkspaceUtil', () => { const workspace: Workspace = await WorkspaceUtil.createWorkspace(core, [glob]); // ==== ASSERTIONS ==== - const workspaceFiles: string[] = workspace.getFilesAndFolders(); + const workspaceFiles: string[] = workspace.getRawFilesAndFolders(); expect(workspaceFiles).toHaveLength(expectation.size); for (const expectedWorkspacePath of expectation.keys()) { expect(workspaceFiles).toContain(expectedWorkspacePath); @@ -224,7 +224,7 @@ describe('WorkspaceUtil', () => { const workspace: Workspace = await WorkspaceUtil.createWorkspace(core, [glob]); // ==== ASSERTIONS ==== - const workspaceFiles: string[] = workspace.getFilesAndFolders(); + const workspaceFiles: string[] = workspace.getRawFilesAndFolders(); expect(workspaceFiles).toHaveLength(expectation.size); for (const expectedWorkspacePath of expectation.keys()) { expect(workspaceFiles).toContain(expectedWorkspacePath); diff --git a/test/stubs/StubEnginePlugins.ts b/test/stubs/StubEnginePlugins.ts index 53881e711..143f60806 100644 --- a/test/stubs/StubEnginePlugins.ts +++ b/test/stubs/StubEnginePlugins.ts @@ -456,12 +456,12 @@ export class TargetDependentEngine1 extends EngineApi.Engine { return Promise.resolve("1.3.0"); } - describeRules(describeOptions: EngineApi.DescribeOptions): Promise { + async describeRules(describeOptions: EngineApi.DescribeOptions): Promise { if (!describeOptions.workspace) { return Promise.resolve([]); } - // Derive a rule for each of the targeted files/folders in the workspace. - return Promise.resolve(describeOptions.workspace.getFilesAndFolders().map(fileOrFolder => { + // Derive a rule for each of the targeted files. + return (await describeOptions.workspace.getTargetedFiles()).map(fileOrFolder => { return { name: `ruleFor${fileOrFolder}`, severityLevel: EngineApi.SeverityLevel.Low, @@ -469,7 +469,7 @@ export class TargetDependentEngine1 extends EngineApi.Engine { description: `Rule synthesized for target "${fileOrFolder}`, resourceUrls: [`https://example.com/${fileOrFolder}`] } - })); + }); } runRules(ruleNames: string[], runOptions: EngineApi.RunOptions): Promise { diff --git a/yarn.lock b/yarn.lock index 57145f44c..74462d5e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1699,12 +1699,12 @@ strip-ansi "6.0.1" ts-retry-promise "^0.8.1" -"@salesforce/code-analyzer-core@0.25.1": - version "0.25.1" - resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-core/-/code-analyzer-core-0.25.1.tgz#06101ea36ab23a54067840851431bdd9fec6f74c" - integrity sha512-Rm1BLksTta7x0jPw8wILE3JgyFuN9PKlclIYU4fC3MP++vDkzQVxQD9jxL3lsXbQDyHKxFu1UdgqSKgsHY0OdQ== +"@salesforce/code-analyzer-core@0.26.0": + version "0.26.0" + resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-core/-/code-analyzer-core-0.26.0.tgz#c9decccaf29e738badd5ed3fcd8763fdb50de9b6" + integrity sha512-CXrldpppksW24NfhN4EXLXLYwfYNrKWHUKiy8TqY+VO6iZl5hyXe0okrc31YVK6NkD3bNeZVcPyipzLM0qC4dQ== dependencies: - "@salesforce/code-analyzer-engine-api" "0.20.0" + "@salesforce/code-analyzer-engine-api" "0.21.0" "@types/js-yaml" "^4.0.9" "@types/node" "^20.0.0" "@types/sarif" "^2.1.7" @@ -1714,26 +1714,26 @@ semver "^7.7.0" xmlbuilder "^15.1.1" -"@salesforce/code-analyzer-engine-api@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-engine-api/-/code-analyzer-engine-api-0.20.0.tgz#2451d15f3ab980842effef9bd35f1bd9b8cfd879" - integrity sha512-DbNNIIN9z0kI5ezOb9x7Za78V5DUtT3prcfog1aZj4MuTmijEhCRL2UAWKG7bndxXLBkuTr4MmibcYVgOky2lg== +"@salesforce/code-analyzer-engine-api@0.21.0": + version "0.21.0" + resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-engine-api/-/code-analyzer-engine-api-0.21.0.tgz#c34aceae7b046662aadf69bf39bb3c38f780289b" + integrity sha512-WIz4KtM8MpzaU7z2qcmnEdkwJ2ZQyvYJq3qcAwbgSp1zN8imuEm6KX/SJFVGoRIVb9hbXqxZodqnvTDZKK6cJA== dependencies: "@types/node" "^20.0.0" "@types/tmp" "^0.2.6" tmp "^0.2.3" -"@salesforce/code-analyzer-eslint-engine@0.20.1": - version "0.20.1" - resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-eslint-engine/-/code-analyzer-eslint-engine-0.20.1.tgz#41908d84e08ec766d24a2b5b970ec9f14e557c9c" - integrity sha512-DBMhqSGCqo+/s4tsBrZoEEceFgQ73nokofC5sKKZVnYbEDiOlJRgPuqX3gdGLFDO3DiKWG+jLWcRqv8+NOZ20g== +"@salesforce/code-analyzer-eslint-engine@0.21.0": + version "0.21.0" + resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-eslint-engine/-/code-analyzer-eslint-engine-0.21.0.tgz#c7b63b64c20cc4517129eec11b0ec2055fee98a4" + integrity sha512-Sb8y+RKq3RVZSAUFIN0uO5wjeRsK9ZAI39bW5T0Ri6F4G5PySbdn6f+8Cr7WU6y4wpB3+H+a/HoB96MSC0ZzbA== dependencies: "@babel/core" "^7.26.7" "@babel/eslint-parser" "^7.26.5" "@eslint/js" "^8.57.1" "@lwc/eslint-plugin-lwc" "^2.1.0" "@lwc/eslint-plugin-lwc-platform" "^5.1.0" - "@salesforce/code-analyzer-engine-api" "0.20.0" + "@salesforce/code-analyzer-engine-api" "0.21.0" "@salesforce/eslint-config-lwc" "^3.7.1" "@salesforce/eslint-plugin-lightning" "^1.0.1" "@types/eslint" "^8.56.10" @@ -1744,59 +1744,55 @@ eslint-plugin-import "^2.31.0" eslint-plugin-jest "^28.11.0" -"@salesforce/code-analyzer-flowtest-engine@0.18.1": - version "0.18.1" - resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-flowtest-engine/-/code-analyzer-flowtest-engine-0.18.1.tgz#b623bb1e0d41a397529743b5811ef8aa0f1e9aff" - integrity sha512-cv2r3vZUcsDKGMxQ2u3WKXzBPWgurLKq1DjGr86imnhRvs+2tsAbMHb5lEDaOOjaXUHjDmf1A2hIObY2oOzHEw== +"@salesforce/code-analyzer-flow-engine@0.19.0": + version "0.19.0" + resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-flow-engine/-/code-analyzer-flow-engine-0.19.0.tgz#a87d30d8f33f3ee09291fa8dfe99774a97305edc" + integrity sha512-TZM1UqOjwA4z2WQaw28wdJ25005yIKJ4uIBnloIpgXjIc/YGJ/Zcgkul954VjBUsQQZwPpEE9ljliSTbyi6kyQ== dependencies: - "@salesforce/code-analyzer-engine-api" "0.20.0" + "@salesforce/code-analyzer-engine-api" "0.21.0" "@types/node" "^20.0.0" "@types/semver" "^7.5.8" - "@types/tmp" "^0.2.6" semver "^7.7.0" - tmp "^0.2.3" -"@salesforce/code-analyzer-pmd-engine@0.21.0": - version "0.21.0" - resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-pmd-engine/-/code-analyzer-pmd-engine-0.21.0.tgz#68653d7cf54d043e0558a80875a2cff78fe09c74" - integrity sha512-StkMAyjzaNdcJDWzwMAubKlq5rnjAM2zLxvBYiC255GoSJhkhbMjN6YHiAy35TpzedhXh73bhY8vaYXF0D1eZQ== +"@salesforce/code-analyzer-pmd-engine@0.22.0": + version "0.22.0" + resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-pmd-engine/-/code-analyzer-pmd-engine-0.22.0.tgz#095f17f4fc08a7852b6f50a75927e0b963b120a7" + integrity sha512-yKiP5OK2RknV/q6INVHmo/sA96FRM6zyyx8ATg3hAGVRLjqGabu6U6cMbEHt8iuZy8cmcfW/3QUwkoOLT2C/0Q== dependencies: - "@salesforce/code-analyzer-engine-api" "0.20.0" + "@salesforce/code-analyzer-engine-api" "0.21.0" "@types/node" "^20.0.0" "@types/semver" "^7.5.8" semver "^7.7.0" -"@salesforce/code-analyzer-regex-engine@0.18.1": - version "0.18.1" - resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-regex-engine/-/code-analyzer-regex-engine-0.18.1.tgz#4e4c97531f93ccd645d1fe2f2e56b36ff19b5893" - integrity sha512-rIhdyYY96ttFdIggXgyES/m5A2ijWH5su7mQqFVzg4sct3BZKdBw+AeypNkQvH2wZm3iggwg9+x5G6fuKcqOZg== +"@salesforce/code-analyzer-regex-engine@0.19.0": + version "0.19.0" + resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-regex-engine/-/code-analyzer-regex-engine-0.19.0.tgz#ec045ecfa1c46eeeeb37e9bd004511863cd6bf65" + integrity sha512-a4/RTrknKO/WUWCD6brZ7BI80J8ggOeICuz6V+CHsqu5zT1zU245yNv3edPxQoKtM3Q/xAoNxhXLJFHLyh184Q== dependencies: - "@salesforce/code-analyzer-engine-api" "0.20.0" + "@salesforce/code-analyzer-engine-api" "0.21.0" "@types/node" "^20.0.0" isbinaryfile "^5.0.4" -"@salesforce/code-analyzer-retirejs-engine@0.18.1": - version "0.18.1" - resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-retirejs-engine/-/code-analyzer-retirejs-engine-0.18.1.tgz#42e5c9cf94e1a39a38f854e9104c8d5131bd5f2e" - integrity sha512-RABH2NCD17ll89klJOsxhLIqha3meiGHhJN9lzclV/7AD/3zeXmCuHV4DwRAilN1D74nsDM0CmY9XrTcSLzxsQ== +"@salesforce/code-analyzer-retirejs-engine@0.19.0": + version "0.19.0" + resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-retirejs-engine/-/code-analyzer-retirejs-engine-0.19.0.tgz#4f682f495f0c760a7121691c233034c0b19160df" + integrity sha512-aOi9KyQ3jtw4nUJvCTktAyu5k2TV3wy1MT/346IgtjEDzNpdhu1IqVsnyW4RHEf7Uo3MucF8ujXGEKxcZtZmWg== dependencies: - "@salesforce/code-analyzer-engine-api" "0.20.0" + "@salesforce/code-analyzer-engine-api" "0.21.0" "@types/node" "^20.0.0" - "@types/tmp" "^0.2.6" isbinaryfile "^5.0.4" node-stream-zip "^1.15.0" retire "^5.2.5" - tmp "^0.2.3" -"@salesforce/code-analyzer-sfge-engine@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-sfge-engine/-/code-analyzer-sfge-engine-0.1.0.tgz#10988052af910b8c76bb8c462d1050dad9c6c786" - integrity sha512-i8v0VEoeX7jk+Dqf/s/uQ1Y6q9sOP7wPtiNW8LyGFLkyQm/FY6XmKWykbfN/QRgPRa39JoJ4J4Qy9Lfe2rASJw== +"@salesforce/code-analyzer-sfge-engine@0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-sfge-engine/-/code-analyzer-sfge-engine-0.2.0.tgz#2337aa0e8f989bc0b36b143ac19fb93531b9a862" + integrity sha512-UUSX52LmKzB7yIcp0l1R1AUN8jVy79dXMbpC0IaFaEd3rUyN61/ifnGlEc3hbrAovL277VX2s9MlzC06zuimew== dependencies: - "@salesforce/code-analyzer-engine-api" "0.20.0" + "@salesforce/code-analyzer-engine-api" "0.21.0" "@types/node" "^20.0.0" - "@types/tmp" "^0.2.6" - tmp "^0.2.3" + "@types/semver" "^7.7.0" + semver "^7.7.1" "@salesforce/core@6.7.6", "@salesforce/core@^6.4.1": version "6.7.6" @@ -2626,6 +2622,11 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== +"@types/semver@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.7.0.tgz#64c441bdae033b378b6eef7d0c3d77c329b9378e" + integrity sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA== + "@types/shelljs@^0.8.15": version "0.8.15" resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.15.tgz#22c6ab9dfe05cec57d8e6cb1a95ea173aee9fcac"