Skip to content

Commit

Permalink
TS: Get ESLint up and running.
Browse files Browse the repository at this point in the history
  • Loading branch information
petervdonovan committed Jan 1, 2022
1 parent f5852ef commit 3096930
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 18 deletions.
11 changes: 11 additions & 0 deletions org.lflang/src/lib/ts/.eslintrc.json
@@ -0,0 +1,11 @@
{
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
]
}
3 changes: 3 additions & 0 deletions org.lflang/src/lib/ts/package.json
Expand Up @@ -26,6 +26,9 @@
"@babel/preset-typescript": "^7.8.3",
"@types/google-protobuf": "^3.7.4",
"@types/node": "^13.9.2",
"@typescript-eslint/eslint-plugin": "^5.8.1",
"@typescript-eslint/parser": "^5.8.1",
"eslint": "^8.5.0",
"typescript": "^3.8.3",
"ts-protoc-gen": "^0.12.0"
},
Expand Down
4 changes: 2 additions & 2 deletions org.lflang/src/org/lflang/generator/DiagnosticReporting.java
Expand Up @@ -39,8 +39,8 @@ private DiagnosticReporting() {
* @param position The position where the message originates.
* @return The given data as a human-readable message.
*/
public static String messageOf(String message, String path, Position position) {
return String.format("%s [%s:%s:%s]", message, path, position.getOneBasedLine(), position.getOneBasedColumn());
public static String messageOf(String message, Path path, Position position) {
return String.format("%s [%s:%s:%s]", message, path.getFileName().toString(), position.getOneBasedLine(), position.getOneBasedColumn());
}

/**
Expand Down
Expand Up @@ -65,7 +65,7 @@ private void reportErrorLine(String line, Iterator<String> it, ErrorReporter err
Integer.parseInt(matcher.group("line")), Integer.parseInt(matcher.group("column"))
);
final String message = DiagnosticReporting.messageOf(
matcher.group("message"), path.getFileName().toString(), generatedFilePosition
matcher.group("message"), path, generatedFilePosition
);
final CodeMap map = maps.get(path);
final DiagnosticSeverity severity = DiagnosticReporting.severityOf(matcher.group("severity"));
Expand Down
2 changes: 1 addition & 1 deletion org.lflang/src/org/lflang/generator/rust/RustValidator.kt
Expand Up @@ -118,7 +118,7 @@ class RustValidator(
for (lfSourcePath: Path in it.lfSourcePaths()) {
errorReporter.report(
message.severity,
DiagnosticReporting.messageOf(message.message, p.toString(), s.start),
DiagnosticReporting.messageOf(message.message, p, s.start),
it.adjusted(lfSourcePath, s.start),
it.adjusted(lfSourcePath, s.end),
)
Expand Down
41 changes: 27 additions & 14 deletions org.lflang/src/org/lflang/generator/ts/TSGenerator.kt
Expand Up @@ -39,9 +39,15 @@ import org.lflang.scoping.LFGlobalScopeProvider
import java.nio.file.Files
import java.util.*
import org.lflang.federated.serialization.SupportedSerializers
import org.lflang.generator.*
import org.lflang.util.LFCommand
import java.io.File
import org.lflang.generator.canGenerate
import org.lflang.generator.CodeMap
import org.lflang.generator.GeneratorBase
import org.lflang.generator.GeneratorResult
import org.lflang.generator.IntegratedBuilder
import org.lflang.generator.LFGeneratorContext
import org.lflang.generator.PrependOperator
import java.nio.file.Path
import kotlin.collections.HashMap

/**
* Generator for TypeScript target.
Expand All @@ -66,7 +72,7 @@ class TSGenerator(
* Names of the configuration files to check for and copy to the generated
* source package root if they cannot be found in the source directory.
*/
val CONFIG_FILES = arrayOf("package.json", "tsconfig.json", "babel.config.js")
val CONFIG_FILES = arrayOf("package.json", "tsconfig.json", "babel.config.js", ".eslintrc.json")

/**
* Files to be copied from the reactor-ts submodule into the generated
Expand Down Expand Up @@ -146,7 +152,7 @@ class TSGenerator(
fileConfig.copyFileFromClassPath("$LIB_PATH/$configFile", configFileDest)
}
}

val codeMaps = HashMap<Path, CodeMap>()
for (federate in federates) {
var tsFileName = fileConfig.name
// TODO(hokeun): Consider using FedFileConfig when enabling federated execution for TypeScript.
Expand All @@ -172,8 +178,9 @@ class TSGenerator(
tsCode.append(reactorGenerator.generateReactor(reactor, federate))
}
tsCode.append(reactorGenerator.generateReactorInstanceAndStart(this.mainDef, mainParameters))
fsa.generateFile(fileConfig.srcGenBasePath.relativize(tsFilePath).toString(),
tsCode.toString())
val codeMap = CodeMap.fromGeneratedCode(tsCode.toString())
codeMaps[tsFilePath] = codeMap
fsa.generateFile(fileConfig.srcGenBasePath.relativize(tsFilePath).toString(), codeMap.generatedCode)

if (targetConfig.dockerOptions != null) {
val dockerFilePath = fileConfig.srcGenPath.resolve("$tsFileName.Dockerfile");
Expand All @@ -182,19 +189,25 @@ class TSGenerator(
fsa.generateFile(fileConfig.srcGenBasePath.relativize(dockerFilePath).toString(), dockerGenerator.generateDockerFileContent())
}
}
// The following check is omitted for Mode.LSP_FAST because this code is unreachable in LSP_FAST mode.
if (!targetConfig.noCompile && context.mode != Mode.LSP_MEDIUM) compile(resource, context)
else context.finish(GeneratorResult.GENERATED_NO_EXECUTABLE.apply(null))
}

private fun compile(resource: Resource, context: LFGeneratorContext) {
if (!context.cancelIndicator.isCanceled) {
if (targetConfig.noCompile) {
context.finish(GeneratorResult.GENERATED_NO_EXECUTABLE.apply(null))
} else {
context.reportProgress(
"Code generation complete. Collecting dependencies...",
IntegratedBuilder.GENERATED_PERCENT_PROGRESS
)
collectDependencies(resource, context)
if (context.mode == Mode.LSP_MEDIUM) {
context.reportProgress("Validating generated code...", IntegratedBuilder.COMPILED_PERCENT_PROGRESS)
TSValidator(tsFileConfig, errorReporter, codeMaps).doValidate(context.cancelIndicator)
context.finish(GeneratorResult.GENERATED_NO_EXECUTABLE.apply(codeMaps))
} else {
compile(context)
}
}
}

private fun compile(context: LFGeneratorContext) {

refreshProject()

Expand Down
112 changes: 112 additions & 0 deletions org.lflang/src/org/lflang/generator/ts/TSValidator.kt
@@ -0,0 +1,112 @@
package org.lflang.generator.ts

import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import org.eclipse.lsp4j.DiagnosticSeverity
import org.lflang.ErrorReporter
import org.lflang.generator.CodeMap
import org.lflang.generator.DiagnosticReporting
import org.lflang.generator.Position
import org.lflang.generator.ValidationStrategy
import org.lflang.generator.Validator
import org.lflang.util.LFCommand
import java.nio.file.Path

@Suppress("ArrayInDataClass") // Data classes here must not be used in data structures such as hashmaps.
class TSValidator(
private val fileConfig: TSFileConfig,
errorReporter: ErrorReporter,
codeMaps: Map<Path, CodeMap>
): Validator(errorReporter, codeMaps) {

companion object {
private val mapper = ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
}

private data class ESLintOutput(
@JsonProperty("filePath") val filePath: String,
@JsonProperty("messages") val messages: Array<ESLintMessage>,
@JsonProperty("errorCount") val errorCount: Int,
@JsonProperty("fatalErrorCount") val fatalErrorCount: Int,
@JsonProperty("warningCount") val warningCount: Int,
@JsonProperty("fixableErrorCount") val fixableErrorCount: Int,
@JsonProperty("fixableWarningCount") val fixableWarningCount: Int,
@JsonProperty("source") val source: String
)

private data class ESLintMessage(
@JsonProperty("ruleId") val ruleId: String?,
@JsonProperty("severity") val _severity: Int,
@JsonProperty("message") val message: String,
@JsonProperty("line") val line: Int,
@JsonProperty("column") val column: Int,
@JsonProperty("nodeType") val nodeType: String?,
@JsonProperty("messageId") val messageId: String?,
@JsonProperty("endLine") val endLine: Int,
@JsonProperty("endColumn") val endColumn: Int,
@JsonProperty("fix") val fix: ESLintFix?
) {
val start: Position = Position.fromOneBased(line, column)
val end: Position = if (endLine >= line) Position.fromOneBased(endLine, endColumn) else start.plus(" ")
val severity: DiagnosticSeverity = when (_severity) {
0 -> DiagnosticSeverity.Information
1 -> DiagnosticSeverity.Warning
2 -> DiagnosticSeverity.Error
else -> DiagnosticSeverity.Warning // This should never happen
}
}

private data class ESLintFix(
@JsonProperty("range") val range: Array<Int>,
@JsonProperty("text") val text: String
)

override val possibleStrategies: Iterable<ValidationStrategy> = listOf(object: ValidationStrategy {
override fun getCommand(generatedFile: Path?): LFCommand? {
return generatedFile?.let {
LFCommand.get(
"npx",
listOf("eslint", "--format", "json", fileConfig.srcGenPath.relativize(it).toString()),
fileConfig.srcGenPath
)
}
}

override fun getErrorReportingStrategy() = DiagnosticReporting.Strategy { _, _, _ -> }

override fun getOutputReportingStrategy() = DiagnosticReporting.Strategy {
validationOutput, errorReporter, map -> validationOutput.lines().filter { it.isNotBlank() }.forEach {
line: String -> mapper.readValue(line, Array<ESLintOutput>::class.java).forEach {
output: ESLintOutput -> output.messages.forEach {
message: ESLintMessage ->
val genPath: Path = fileConfig.srcGenPath.resolve(output.filePath)
map[genPath]?.let {
codeMap ->
codeMap.lfSourcePaths().forEach {
val lfStart = codeMap.adjusted(it, message.start)
val lfEnd = codeMap.adjusted(it, message.end)
if (!lfStart.equals(Position.ORIGIN)) {
errorReporter.report(
message.severity,
DiagnosticReporting.messageOf(message.message, genPath, message.start),
lfStart,
if (lfEnd > lfStart) lfEnd else lfStart.plus(" "),
)
}
}
}
}
}
}
}

override fun isFullBatch(): Boolean = false // ESLint permits glob patterns. We could make this full-batch if desired.

override fun getPriority(): Int = 0

})
override val buildReportingStrategies: Pair<DiagnosticReporting.Strategy, DiagnosticReporting.Strategy>
get() = Pair(DiagnosticReporting.Strategy {_, _, _ -> }, DiagnosticReporting.Strategy {_, _, _ -> }) // TODO

}

0 comments on commit 3096930

Please sign in to comment.