software.amazon.smithy.lsp.project.SmithyFileDependenciesIndex Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of smithy-language-server Show documentation
Show all versions of smithy-language-server Show documentation
LSP implementation for smithy
The newest version!
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package software.amazon.smithy.lsp.project;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.node.Node;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.shapes.ToShapeId;
import software.amazon.smithy.model.traits.Trait;
import software.amazon.smithy.model.validation.ValidatedResult;
/**
* An index that caches rebuild dependency relationships between Smithy files,
* shapes, and traits.
*
* This is specifically for the following scenarios:
*
* - A file applies traits to shapes in other files
* - If that file changes, the applied traits need to be removed before the
* file is reloaded, so there aren't duplicate traits.
* - A file has shapes with traits applied in other files
* - If that file changes, the traits need to be re-applied when the model is
* re-assembled, so they aren't lost.
* - Either 1 or 2, but specifically with list traits
* - List traits are merged via
* trait conflict resolution . For these traits, all files that contain
* parts of the list trait must be fully reloaded, since we can only remove
* the whole trait, not parts of it.
*
*/
final class SmithyFileDependenciesIndex {
private final Map> filesToDependentFiles;
private final Map> shapeIdsToDependenciesFiles;
private final Map>> filesToTraitsTheyApply;
private final Map> shapesToAppliedTraitsInOtherFiles;
SmithyFileDependenciesIndex() {
this.filesToDependentFiles = new HashMap<>(0);
this.shapeIdsToDependenciesFiles = new HashMap<>(0);
this.filesToTraitsTheyApply = new HashMap<>(0);
this.shapesToAppliedTraitsInOtherFiles = new HashMap<>(0);
}
private SmithyFileDependenciesIndex(
Map> filesToDependentFiles,
Map> shapeIdsToDependenciesFiles,
Map>> filesToTraitsTheyApply,
Map> shapesToAppliedTraitsInOtherFiles
) {
this.filesToDependentFiles = filesToDependentFiles;
this.shapeIdsToDependenciesFiles = shapeIdsToDependenciesFiles;
this.filesToTraitsTheyApply = filesToTraitsTheyApply;
this.shapesToAppliedTraitsInOtherFiles = shapesToAppliedTraitsInOtherFiles;
}
Set getDependentFiles(String path) {
return filesToDependentFiles.getOrDefault(path, Collections.emptySet());
}
Set getDependenciesFiles(ToShapeId toShapeId) {
return shapeIdsToDependenciesFiles.getOrDefault(toShapeId.toShapeId(), Collections.emptySet());
}
Map> getAppliedTraitsInFile(String path) {
return filesToTraitsTheyApply.getOrDefault(path, Collections.emptyMap());
}
List getTraitsAppliedInOtherFiles(ToShapeId toShapeId) {
return shapesToAppliedTraitsInOtherFiles.getOrDefault(toShapeId.toShapeId(), Collections.emptyList());
}
// TODO: Make this take care of metadata too
static SmithyFileDependenciesIndex compute(ValidatedResult modelResult) {
if (modelResult.getResult().isEmpty()) {
return new SmithyFileDependenciesIndex();
}
SmithyFileDependenciesIndex index = new SmithyFileDependenciesIndex(
new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>());
Model model = modelResult.getResult().get();
for (Shape shape : model.toSet()) {
String shapeSourceFilename = shape.getSourceLocation().getFilename();
for (Trait traitApplication : shape.getAllTraits().values()) {
// We only care about trait applications in the source files
if (traitApplication.isSynthetic()) {
continue;
}
Node traitNode = traitApplication.toNode();
if (traitNode.isArrayNode()) {
for (Node element : traitNode.expectArrayNode()) {
String elementSourceFilename = element.getSourceLocation().getFilename();
if (!elementSourceFilename.equals(shapeSourceFilename)) {
index.filesToDependentFiles.computeIfAbsent(elementSourceFilename, (k) -> new HashSet<>())
.add(shapeSourceFilename);
index.shapeIdsToDependenciesFiles.computeIfAbsent(shape.getId(), (k) -> new HashSet<>())
.add(elementSourceFilename);
}
}
} else {
String traitSourceFilename = traitApplication.getSourceLocation().getFilename();
if (!traitSourceFilename.equals(shapeSourceFilename)) {
index.shapesToAppliedTraitsInOtherFiles.computeIfAbsent(shape.getId(), (k) -> new ArrayList<>())
.add(traitApplication);
index.filesToTraitsTheyApply.computeIfAbsent(traitSourceFilename, (k) -> new HashMap<>())
.computeIfAbsent(shape.getId(), (k) -> new ArrayList<>())
.add(traitApplication);
}
}
}
}
return index;
}
}