package com.intellij.lang.javascript.linter.tslint.service;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.intellij.idea.RareLogger;
import com.intellij.lang.javascript.linter.JSLinterUtil;
import com.intellij.lang.javascript.linter.tslint.config.TsLintState;
import com.intellij.lang.javascript.linter.tslint.execution.TsLintConfigFileSearcher;
import com.intellij.lang.javascript.linter.tslint.execution.TsLintOutputJsonParser;
import com.intellij.lang.javascript.linter.tslint.execution.TsLinterError;
import com.intellij.lang.javascript.linter.tslint.service.commands.TsLintFixErrorsCommand;
import com.intellij.lang.javascript.linter.tslint.service.commands.TsLintGetErrorsCommand;
import com.intellij.lang.javascript.linter.tslint.service.protocol.TsLintLanguageServiceProtocol;
import com.intellij.lang.javascript.service.*;
import com.intellij.lang.javascript.service.protocol.JSLanguageServiceAnswer;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.concurrency.FixedFuture;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.text.SemVer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Future;
public final class TsLintLanguageService extends JSLanguageServiceBase {
@NotNull private final static Logger LOG = RareLogger.wrap(Logger.getInstance("#com.intellij.lang.javascript.linter.tslint.service.TsLintLanguageService"), false);
@NotNull
private final TsLintConfigFileSearcher myConfigFileSearcher;
@NotNull
public static TsLintLanguageService getService(@NotNull Project project) {
return ServiceManager.getService(project, TsLintLanguageService.class);
}
public TsLintLanguageService(@NotNull Project project) {
super(project);
myConfigFileSearcher = new TsLintConfigFileSearcher();
}
@NotNull
@Override
protected String getProcessName() {
return "TSLint";
}
@NotNull
@Override
protected JSLanguageServiceQueue.ServiceInfoReporter createDefaultReporter() {
return new NotificationLanguageServiceReporter(JSLinterUtil.NOTIFICATION_GROUP, super.createDefaultReporter());
}
public final Future<List<TsLinterError>> highlight(@Nullable VirtualFile virtualFile,
@Nullable VirtualFile config,
@Nullable String content) {
final JSLanguageServiceQueue process = getProcess();
final MyParameters parameters = MyParameters.checkParameters(virtualFile, config, process);
if (parameters.getErrors() != null) return new FixedFuture<>(parameters.getErrors());
TsLintGetErrorsCommand command = new TsLintGetErrorsCommand(parameters.getPath(), parameters.getConfigPath(),
StringUtil.notNullize(content));
assert process != null;
return process.execute(command, createHighlightProcessor(parameters.getPath()));
}
public final Future<List<TsLinterError>> highlightAndFix(@Nullable VirtualFile virtualFile, @NotNull TsLintState state) {
VirtualFile config = virtualFile == null ? null : myConfigFileSearcher.getConfig(state, virtualFile);
final JSLanguageServiceQueue process = getProcess();
final MyParameters parameters = MyParameters.checkParameters(virtualFile, config, process);
if (parameters.getErrors() != null) return new FixedFuture<>(parameters.getErrors());
//doesn't pass content (file should be saved before)
TsLintFixErrorsCommand command = new TsLintFixErrorsCommand(parameters.getPath(), parameters.getConfigPath());
assert process != null;
return process.execute(command, createHighlightProcessor(parameters.getPath()));
}
private static class MyParameters {
@NotNull private final String myConfigPath;
@NotNull private final String myPath;
@Nullable private final List<TsLinterError> myErrors;
private MyParameters(@NotNull String path, @NotNull String configPath,
@Nullable List<TsLinterError> errors) {
myConfigPath = configPath;
myPath = path;
myErrors = errors;
}
public static MyParameters checkParameters(@Nullable VirtualFile virtualFile, @Nullable VirtualFile config,
@Nullable JSLanguageServiceQueue process) {
String error;
if (process == null) {
error = "Can not create language service";
} else if (virtualFile == null || !virtualFile.isInLocalFileSystem()) {
error = "Path not specified";
} else if (config == null) {
error = "Config file was not found.";
} else {
final String configPath = JSLanguageServiceUtil.normalizeNameAndPath(config);
final String path = JSLanguageServiceUtil.normalizeNameAndPath(virtualFile);
if (configPath != null && path != null) {
return new MyParameters(path, configPath, null);
}
error = "Can not work with the path: " + (path != null ? path : configPath);
}
return new MyParameters("", "", Collections.singletonList(new TsLinterError(error)));
}
@NotNull
public String getConfigPath() {
return myConfigPath;
}
@NotNull
public String getPath() {
return myPath;
}
@Nullable
public List<TsLinterError> getErrors() {
return myErrors;
}
}
@NotNull
private static JSLanguageServiceCommandProcessor<List<TsLinterError>> createHighlightProcessor(@NotNull String path) {
return (object, answer) -> parseResults(answer, path);
}
@Nullable
private static List<TsLinterError> parseResults(@NotNull JSLanguageServiceAnswer answer, @NotNull String path) {
final JsonObject element = answer.getElement();
final JsonElement error = element.get("error");
if (error != null) {
return Collections.singletonList(new TsLinterError(error.getAsString()));
}
final JsonElement body = parseBody(element);
if (body == null) return null;
final String version = element.get("version").getAsString();
final SemVer tsLintVersion = SemVer.parseFromText(version);
final boolean isZeroBased = TsLintOutputJsonParser.isVersionZeroBased(tsLintVersion);
final TsLintOutputJsonParser parser = new TsLintOutputJsonParser(path, body, isZeroBased);
return ContainerUtil.newArrayList(parser.getErrors());
}
private static JsonElement parseBody(@NotNull JsonObject element) {
final JsonElement body = element.get("body");
if (body == null) {
//we do not currently treat empty body as error in protocol
return null;
} else {
if (body.isJsonPrimitive() && body.getAsJsonPrimitive().isString()) {
final String bodyContent = StringUtil.unquoteString(body.getAsJsonPrimitive().getAsString());
if (!StringUtil.isEmptyOrSpaces(bodyContent)) {
try {
return new JsonParser().parse(bodyContent);
} catch (JsonParseException e) {
LOG.info(String.format("Problem parsing body: '%s'\n%s", body, e.getMessage()), e);
}
}
} else {
LOG.info(String.format("Error body type, should be a string with json inside. Body:'%s'", body.getAsString()));
}
}
return null;
}
@Override
protected final JSLanguageServiceQueue createLanguageServiceQueue() {
TsLintLanguageServiceProtocol protocol = new TsLintLanguageServiceProtocol(myProject, (el) -> {
});
return new JSLanguageServiceQueueImpl(myProject, protocol, myProcessConnector, myDefaultReporter,
new JSLanguageServiceDefaultCacheData());
}
@Override
protected final boolean needInitToolWindow() {
return false;
}
}