From 1052a0b0c507e2cb5022baf42744c3b76a4ddb04 Mon Sep 17 00:00:00 2001 From: Adva Oren Date: Mon, 27 Apr 2026 14:51:49 +0300 Subject: [PATCH] feat(python): add pyproject.toml support and uv path settings (TC-3860) Add pyproject.toml recognition as a Python manifest across all plugin code paths (stack analysis, component analysis, exclude/SA intentions). Add PyprojectCAAnnotator for TOML-based dependency parsing and vulnerability annotations. Add uv executable path setting to the settings UI with TRUSTIFY_DA_UV_PATH system property propagation. Bump trustify-da-java-client to 0.0.17-SNAPSHOT and api-spec to 2.0.7 for pyproject.toml pip report support and uv provider. Co-Authored-By: Claude Opus 4.6 --- gradle/libs.versions.toml | 4 +- .../componentanalysis/CAAnnotator.java | 2 +- .../ExcludeManifestIntentionAction.java | 1 + .../componentanalysis/SAIntentionAction.java | 1 + .../pypi/PyprojectCAAnnotator.java | 260 ++++++++++++++++++ .../pypi/PyprojectCAInspection.java | 39 +++ .../pypi/PyprojectCAIntentionAction.java | 70 +++++ .../tools/intellij/exhort/ApiService.java | 9 +- .../settings/ApiSettingsComponent.java | 21 ++ .../settings/ApiSettingsConfigurable.java | 3 + .../intellij/settings/ApiSettingsState.java | 1 + .../intellij/stackanalysis/SaAction.java | 1 + .../tools/intellij/stackanalysis/SaUtils.java | 2 + src/main/resources/META-INF/plugin.xml | 9 + .../ManifestExclusionManagerSimpleTest.java | 2 +- 15 files changed, 419 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/jboss/tools/intellij/componentanalysis/pypi/PyprojectCAAnnotator.java create mode 100644 src/main/java/org/jboss/tools/intellij/componentanalysis/pypi/PyprojectCAInspection.java create mode 100644 src/main/java/org/jboss/tools/intellij/componentanalysis/pypi/PyprojectCAIntentionAction.java diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c9788b40..0222da2b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,8 +3,8 @@ caffeine = "3.1.8" commons-compress = "1.21" commons-io = "2.16.1" -trustify-da-api-spec = "2.0.2" -trustify-da-java-client = "0.0.15" +trustify-da-api-spec = "2.0.7" +trustify-da-java-client = "0.0.17-SNAPSHOT" github-api = "1.314" junit = "4.13.2" mockito = "4.11.0" diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/CAAnnotator.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/CAAnnotator.java index 296eec87..93ea1f45 100644 --- a/src/main/java/org/jboss/tools/intellij/componentanalysis/CAAnnotator.java +++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/CAAnnotator.java @@ -524,7 +524,7 @@ public static String getPackageManager(String file) { case "pom.xml" -> "maven"; case "package.json" -> "npm"; case "go.mod" -> "go"; - case "requirements.txt" -> "python"; + case "requirements.txt", "pyproject.toml" -> "python"; case "build.gradle", "build.gradle.kts" -> "gradle"; case "Cargo.toml" -> "cargo"; default -> null; diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/ExcludeManifestIntentionAction.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/ExcludeManifestIntentionAction.java index cdcd7ae8..35124b67 100644 --- a/src/main/java/org/jboss/tools/intellij/componentanalysis/ExcludeManifestIntentionAction.java +++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/ExcludeManifestIntentionAction.java @@ -48,6 +48,7 @@ public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file "package.json".equals(fileName) || "go.mod".equals(fileName) || "requirements.txt".equals(fileName) || + "pyproject.toml".equals(fileName) || "build.gradle".equals(fileName) || "build.gradle.kts".equals(fileName) || "Cargo.toml".equals(fileName); diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/SAIntentionAction.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/SAIntentionAction.java index 6bb50637..c4a0ecc0 100644 --- a/src/main/java/org/jboss/tools/intellij/componentanalysis/SAIntentionAction.java +++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/SAIntentionAction.java @@ -47,6 +47,7 @@ public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file || "package.json".equals(file.getName()) || "go.mod".equals(file.getName()) || "requirements.txt".equals(file.getName()) + || "pyproject.toml".equals(file.getName()) || "build.gradle".equals(file.getName()) || "build.gradle.kts".equals(file.getName()) || "Cargo.toml".equals(file.getName()); diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/pypi/PyprojectCAAnnotator.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/pypi/PyprojectCAAnnotator.java new file mode 100644 index 00000000..5a9e7cab --- /dev/null +++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/pypi/PyprojectCAAnnotator.java @@ -0,0 +1,260 @@ +/******************************************************************************* + * Copyright (c) 2025 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ + +package org.jboss.tools.intellij.componentanalysis.pypi; + +import com.intellij.openapi.editor.Document; +import com.intellij.psi.PsiComment; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiTreeUtil; +import io.github.guacsec.trustifyda.api.v5.DependencyReport; +import org.jboss.tools.intellij.componentanalysis.CAAnnotator; +import org.jboss.tools.intellij.componentanalysis.CAIntentionAction; +import org.jboss.tools.intellij.componentanalysis.CAUpdateManifestIntentionAction; +import org.jboss.tools.intellij.componentanalysis.Dependency; +import org.jboss.tools.intellij.componentanalysis.LicenseUpdateIntentionAction; +import org.jboss.tools.intellij.componentanalysis.VulnerabilitySource; +import org.jetbrains.annotations.Nullable; +import org.toml.lang.psi.TomlArray; +import org.toml.lang.psi.TomlKeySegment; +import org.toml.lang.psi.TomlKeyValue; +import org.toml.lang.psi.TomlLiteral; +import org.toml.lang.psi.TomlTable; +import org.toml.lang.psi.TomlTableHeader; +import org.toml.lang.psi.TomlKey; +import org.toml.lang.psi.TomlValue; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.jboss.tools.intellij.componentanalysis.CAUtil.TRUSTIFY_DA_IGNORE; +import static org.jboss.tools.intellij.componentanalysis.CAUtil.EXHORT_IGNORE; + +public class PyprojectCAAnnotator extends CAAnnotator { + + private static final String PYPROJECT_TOML = "pyproject.toml"; + private static final String PROJECT = "project"; + private static final String DEPENDENCIES = "dependencies"; + private static final String OPTIONAL_DEPENDENCIES = "optional-dependencies"; + + // PEP 508: name followed by optional extras, then optional version specifier + // Matches: "flask", "flask>=2.0", "requests[security]~=2.28", "numpy ==1.20.*" + private static final Pattern DEP_PATTERN = Pattern.compile( + "^([A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?)" + // package name + "(\\[.*?])?" + // optional extras + "\\s*(.*)$" // version specifier (rest of string) + ); + + @Override + protected String getInspectionShortName() { + return PyprojectCAInspection.SHORT_NAME; + } + + @Override + protected Map> getDependencies(PsiFile file) { + if (!PYPROJECT_TOML.equals(file.getName())) { + return Collections.emptyMap(); + } + + Map> resultMap = new HashMap<>(); + Set ignoredDeps = getIgnoredDependencies(file); + + List tables = PsiTreeUtil.getChildrenOfTypeAsList(file, TomlTable.class); + for (TomlTable table : tables) { + TomlTableHeader header = table.getHeader(); + TomlKey key = header.getKey(); + if (key == null) { + continue; + } + + List segments = key.getSegments(); + + // [project] table — look for "dependencies" key + if (segments.size() == 1 && PROJECT.equals(segments.get(0).getName())) { + for (TomlKeyValue kv : PsiTreeUtil.getChildrenOfTypeAsList(table, TomlKeyValue.class)) { + if (DEPENDENCIES.equals(kv.getKey().getText())) { + parseDependencyArray(kv.getValue(), ignoredDeps, resultMap); + } + } + } + + // [project.optional-dependencies] table — all values are dependency arrays + if (segments.size() == 2 + && PROJECT.equals(segments.get(0).getName()) + && OPTIONAL_DEPENDENCIES.equals(segments.get(1).getName())) { + for (TomlKeyValue kv : PsiTreeUtil.getChildrenOfTypeAsList(table, TomlKeyValue.class)) { + parseDependencyArray(kv.getValue(), ignoredDeps, resultMap); + } + } + } + + return resultMap; + } + + private void parseDependencyArray(TomlValue value, Set ignoredDeps, + Map> resultMap) { + if (!(value instanceof TomlArray array)) { + return; + } + + for (TomlValue element : array.getElements()) { + if (!(element instanceof TomlLiteral literal)) { + continue; + } + + String text = literal.getText(); + if (text.length() < 2 || !text.startsWith("\"") || !text.endsWith("\"")) { + continue; + } + + String depString = text.substring(1, text.length() - 1); + String name = parseName(depString); + if (name == null || ignoredDeps.contains(name)) { + continue; + } + + String version = parseVersion(depString); + Dependency dp = new Dependency("pypi", null, name, version); + resultMap.computeIfAbsent(dp, k -> new LinkedList<>()).add(literal); + } + } + + static String parseName(String depString) { + Matcher m = DEP_PATTERN.matcher(depString.trim()); + if (m.matches()) { + return m.group(1).toLowerCase(); + } + return null; + } + + private static String parseVersion(String depString) { + Matcher m = DEP_PATTERN.matcher(depString.trim()); + if (m.matches()) { + String versionSpec = m.group(4); + if (versionSpec != null && !versionSpec.isBlank()) { + return versionSpec.trim(); + } + } + return null; + } + + private Set getIgnoredDependencies(PsiFile file) { + Set ignoredDeps = new HashSet<>(); + Collection comments = PsiTreeUtil.collectElementsOfType(file, PsiComment.class); + for (PsiComment comment : comments) { + String commentText = comment.getText(); + if (commentText.contains(TRUSTIFY_DA_IGNORE) || commentText.contains(EXHORT_IGNORE)) { + TomlLiteral literal = findLiteralOnSameLine(file, comment); + if (literal != null) { + String text = literal.getText(); + if (text.length() >= 2 && text.startsWith("\"") && text.endsWith("\"")) { + String name = parseName(text.substring(1, text.length() - 1)); + if (name != null) { + ignoredDeps.add(name); + } + } + } + } + } + return ignoredDeps; + } + + private TomlLiteral findLiteralOnSameLine(PsiFile file, PsiComment comment) { + Document document = PsiDocumentManager.getInstance(comment.getProject()).getDocument(file); + if (document == null) { + return null; + } + int commentLine = document.getLineNumber(comment.getTextRange().getStartOffset()); + Collection literals = PsiTreeUtil.collectElementsOfType(file, TomlLiteral.class); + for (TomlLiteral literal : literals) { + int literalLine = document.getLineNumber(literal.getTextRange().getStartOffset()); + if (commentLine == literalLine) { + return literal; + } + } + return null; + } + + @Override + protected CAIntentionAction createQuickFix(PsiElement element, VulnerabilitySource source, DependencyReport report) { + return new PyprojectCAIntentionAction(element, source, report); + } + + @Override + protected CAUpdateManifestIntentionAction patchManifest(PsiElement element, DependencyReport report) { + return null; + } + + @Override + protected boolean isQuickFixApplicable(PsiElement element) { + if (!(element instanceof TomlLiteral literal)) { + return false; + } + String text = literal.getText(); + if (text.length() < 2 || !text.startsWith("\"") || !text.endsWith("\"")) { + return false; + } + String depString = text.substring(1, text.length() - 1); + String version = parseVersion(depString); + return version != null; + } + + @Override + protected @Nullable PsiElement getLicenseFieldPsiElement(PsiFile file) { + List tables = PsiTreeUtil.getChildrenOfTypeAsList(file, TomlTable.class); + for (TomlTable table : tables) { + TomlTableHeader header = table.getHeader(); + TomlKey key = header.getKey(); + if (key == null) { + continue; + } + List segments = key.getSegments(); + if (segments.size() == 1 && PROJECT.equals(segments.get(0).getName())) { + for (TomlKeyValue kv : PsiTreeUtil.getChildrenOfTypeAsList(table, TomlKeyValue.class)) { + if ("license".equals(kv.getKey().getText())) { + TomlValue value = kv.getValue(); + if (value instanceof TomlLiteral) { + return value; + } + } + } + } + } + return null; + } + + @Override + protected @Nullable LicenseUpdateIntentionAction createLicenseUpdateFix(PsiElement element, String newLicense) { + if (!(element instanceof TomlLiteral)) { + return null; + } + return new LicenseUpdateIntentionAction(element, newLicense, (el, license) -> { + Document doc = PsiDocumentManager.getInstance(el.getProject()) + .getDocument(el.getContainingFile()); + if (doc != null) { + int start = el.getTextRange().getStartOffset(); + int end = el.getTextRange().getEndOffset(); + doc.replaceString(start, end, "\"" + license + "\""); + PsiDocumentManager.getInstance(el.getProject()).commitDocument(doc); + } + }); + } +} diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/pypi/PyprojectCAInspection.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/pypi/PyprojectCAInspection.java new file mode 100644 index 00000000..a6223f2f --- /dev/null +++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/pypi/PyprojectCAInspection.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2025 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ + +package org.jboss.tools.intellij.componentanalysis.pypi; + +import com.intellij.codeHighlighting.HighlightDisplayLevel; +import com.intellij.codeInspection.LocalInspectionTool; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +public class PyprojectCAInspection extends LocalInspectionTool { + + @NonNls + public static final String SHORT_NAME = "PyprojectCAInspection"; + + @Override + public @Nls(capitalization = Nls.Capitalization.Sentence) @NotNull String getGroupDisplayName() { + return "Python"; + } + + @Override + public @NonNls @NotNull String getShortName() { + return SHORT_NAME; + } + + @Override + public @NotNull HighlightDisplayLevel getDefaultLevel() { + return HighlightDisplayLevel.ERROR; + } +} diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/pypi/PyprojectCAIntentionAction.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/pypi/PyprojectCAIntentionAction.java new file mode 100644 index 00000000..513b782e --- /dev/null +++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/pypi/PyprojectCAIntentionAction.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (c) 2025 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ + +package org.jboss.tools.intellij.componentanalysis.pypi; + +import com.intellij.codeInsight.intention.FileModifier; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import io.github.guacsec.trustifyda.api.v5.DependencyReport; +import org.jboss.tools.intellij.componentanalysis.CAIntentionAction; +import org.jboss.tools.intellij.componentanalysis.VulnerabilitySource; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.toml.lang.psi.TomlLiteral; + +public final class PyprojectCAIntentionAction extends CAIntentionAction { + + PyprojectCAIntentionAction(PsiElement element, VulnerabilitySource source, DependencyReport report) { + super(element, source, report); + } + + @Override + protected void updateVersion(@NotNull Project project, Editor editor, PsiFile file, String version) { + if (version == null || !(element instanceof TomlLiteral literal)) { + return; + } + + String text = literal.getText(); + if (text.length() < 2 || !text.startsWith("\"") || !text.endsWith("\"")) { + return; + } + + String depString = text.substring(1, text.length() - 1); + String name = PyprojectCAAnnotator.parseName(depString); + if (name == null) { + return; + } + + String newText = "\"" + name + "==" + version + "\""; + Document document = PsiDocumentManager.getInstance(project).getDocument(file); + if (document != null) { + int start = literal.getTextRange().getStartOffset(); + int end = literal.getTextRange().getEndOffset(); + document.replaceString(start, end, newText); + PsiDocumentManager.getInstance(project).commitDocument(document); + } + } + + @Override + protected @Nullable FileModifier createCAIntentionActionInCopy(PsiElement element) { + return new PyprojectCAIntentionAction(element, this.source, this.report); + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + return file != null && "pyproject.toml".equals(file.getName()) && element instanceof TomlLiteral; + } +} diff --git a/src/main/java/org/jboss/tools/intellij/exhort/ApiService.java b/src/main/java/org/jboss/tools/intellij/exhort/ApiService.java index b4cf5fd7..c0fb7985 100644 --- a/src/main/java/org/jboss/tools/intellij/exhort/ApiService.java +++ b/src/main/java/org/jboss/tools/intellij/exhort/ApiService.java @@ -239,6 +239,11 @@ private void setRequestProperties(final String manifestName) { System.clearProperty("TRUSTIFY_DA_PYTHON_PATH"); System.clearProperty("TRUSTIFY_DA_PIP_PATH"); } + if (settings.uvPath != null && !settings.uvPath.isBlank()) { + System.setProperty("TRUSTIFY_DA_UV_PATH", settings.uvPath); + } else { + System.clearProperty("TRUSTIFY_DA_UV_PATH"); + } if (settings.usePythonVirtualEnv) { System.setProperty("TRUSTIFY_DA_PYTHON_VIRTUAL_ENV", "true"); if (settings.pythonInstallBestEfforts) { @@ -250,14 +255,14 @@ private void setRequestProperties(final String manifestName) { System.clearProperty("TRUSTIFY_DA_PYTHON_VIRTUAL_ENV"); System.clearProperty("TRUSTIFY_DA_PYTHON_INSTALL_BEST_EFFORTS"); } - if ("requirements.txt".equals(manifestName)) { + if ("requirements.txt".equals(manifestName) || "pyproject.toml".equals(manifestName)) { if (settings.pythonMatchManifestVersions) { System.setProperty("MATCH_MANIFEST_VERSIONS", "true"); } else { System.setProperty("MATCH_MANIFEST_VERSIONS","false"); } } - if (!"go.mod".equals(manifestName) && !"requirements.txt".equals(manifestName)) { + if (!"go.mod".equals(manifestName) && !"requirements.txt".equals(manifestName) && !"pyproject.toml".equals(manifestName)) { System.clearProperty("MATCH_MANIFEST_VERSIONS"); } if (settings.licenseCheckEnabled) { diff --git a/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsComponent.java b/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsComponent.java index 04060642..cd694623 100644 --- a/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsComponent.java +++ b/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsComponent.java @@ -56,6 +56,8 @@ public class ApiSettingsComponent { + "
Specifies absolute path of python executable."; private final static String pipPathLabel = "Pip > Executable: Path" + "
Specifies absolute path of pip executable."; + private final static String uvPathLabel = "uv > Executable: Path" + + "
Specifies absolute path of uv executable."; private final static String usePython2Label = "Python > Executable: Version" + "
Specifies if using python 2.x."; private final static String usePythonVirtualEnvLabel = "Python > Virtual Environment" @@ -100,6 +102,7 @@ public class ApiSettingsComponent { private final JBCheckBox goMatchManifestVersionsCheck; private final TextFieldWithBrowseButton pythonPathText; private final TextFieldWithBrowseButton pipPathText; + private final TextFieldWithBrowseButton uvPathText; private final JBCheckBox usePython2Check; private final JBCheckBox usePythonVirtualEnvCheck; private final JBCheckBox pythonInstallBestEffortsCheck; @@ -191,6 +194,13 @@ public ApiSettingsComponent() { TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT ); + uvPathText = new TextFieldWithBrowseButton(); + uvPathText.addBrowseFolderListener( + null, + FileChooserDescriptorFactory.singleFile(), + TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT + ); + usePython2Check = new JBCheckBox("Use python 2.x"); usePythonVirtualEnvCheck = new JBCheckBox("Use python virtual environment"); @@ -295,6 +305,8 @@ public ApiSettingsComponent() { .addVerticalGap(10) .addLabeledComponent(new JBLabel(pipPathLabel), pipPathText, 1, true) .addVerticalGap(10) + .addLabeledComponent(new JBLabel(uvPathLabel), uvPathText, 1, true) + .addVerticalGap(10) .addLabeledComponent(new JBLabel(usePython2Label), usePython2Check, 1, true) .addVerticalGap(10) .addLabeledComponent(new JBLabel(usePythonVirtualEnvLabel), usePythonVirtualEnvCheck, 1, true) @@ -439,6 +451,15 @@ public void setPipPathText(@NotNull String text) { pipPathText.setText(text); } + @NotNull + public String getUvPathText() { + return uvPathText.getText(); + } + + public void setUvPathText(@NotNull String text) { + uvPathText.setText(text); + } + public boolean getUsePython2Check() { return usePython2Check.isSelected(); } diff --git a/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsConfigurable.java b/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsConfigurable.java index dce33ff0..487260d2 100644 --- a/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsConfigurable.java +++ b/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsConfigurable.java @@ -57,6 +57,7 @@ public boolean isModified() { modified |= settingsComponent.getGoMatchManifestVersionsCheck() != settings.goMatchManifestVersions; modified |= !settingsComponent.getPythonPathText().equals(settings.pythonPath); modified |= !settingsComponent.getPipPathText().equals(settings.pipPath); + modified |= !Objects.equals(settingsComponent.getUvPathText(), settings.uvPath); modified |= settingsComponent.getUsePython2Check() != settings.usePython2; modified |= settingsComponent.getUsePythonVirtualEnvCheck() != settings.usePythonVirtualEnv; modified |= settingsComponent.getPythonInstallBestEffortsCheck() != settings.pythonInstallBestEfforts; @@ -91,6 +92,7 @@ public void apply() { settings.goMatchManifestVersions = settingsComponent.getGoMatchManifestVersionsCheck(); settings.pythonPath = settingsComponent.getPythonPathText(); settings.pipPath = settingsComponent.getPipPathText(); + settings.uvPath = settingsComponent.getUvPathText(); settings.usePython2 = settingsComponent.getUsePython2Check(); settings.usePythonVirtualEnv = settingsComponent.getUsePythonVirtualEnvCheck(); settings.pythonInstallBestEfforts = settingsComponent.getPythonInstallBestEffortsCheck(); @@ -154,6 +156,7 @@ public void reset() { settingsComponent.setGoMatchManifestVersionsCheck(settings.goMatchManifestVersions); settingsComponent.setPythonPathText(settings.pythonPath != null ? settings.pythonPath : ""); settingsComponent.setPipPathText(settings.pipPath != null ? settings.pipPath : ""); + settingsComponent.setUvPathText(settings.uvPath != null ? settings.uvPath : ""); settingsComponent.setUsePython2Check(settings.usePython2); settingsComponent.setUsePythonVirtualEnvCheck(settings.usePythonVirtualEnv); settingsComponent.setPythonInstallBestEffortsCheck(settings.pythonInstallBestEfforts); diff --git a/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsState.java b/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsState.java index 2a2dafe6..c0a2d702 100644 --- a/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsState.java +++ b/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsState.java @@ -49,6 +49,7 @@ public final class ApiSettingsState implements PersistentStateComponent + + + + + diff --git a/src/test/java/org/jboss/tools/intellij/componentanalysis/ManifestExclusionManagerSimpleTest.java b/src/test/java/org/jboss/tools/intellij/componentanalysis/ManifestExclusionManagerSimpleTest.java index 40e4a487..d97ea75b 100644 --- a/src/test/java/org/jboss/tools/intellij/componentanalysis/ManifestExclusionManagerSimpleTest.java +++ b/src/test/java/org/jboss/tools/intellij/componentanalysis/ManifestExclusionManagerSimpleTest.java @@ -104,7 +104,7 @@ public void testGlobPatternBehavior_SpecificPaths() { @Test public void testGlobPatternBehavior_MultipleFileTypes() { - String[] manifestFiles = {"pom.xml", "package.json", "go.mod", "requirements.txt", "build.gradle", "build.gradle.kts"}; + String[] manifestFiles = {"pom.xml", "package.json", "go.mod", "requirements.txt", "pyproject.toml", "build.gradle", "build.gradle.kts"}; for (String fileName : manifestFiles) { PathMatcher exactMatcher = FileSystems.getDefault().getPathMatcher("glob:" + fileName);