From 146e3eb8582b80a3b0420c00941a26854c0d5312 Mon Sep 17 00:00:00 2001 From: Joao Ferreira Date: Wed, 6 May 2026 11:48:10 +0200 Subject: [PATCH] fix: normalize line endings in check generators to produce identical output regardless of platform --- .../ddk/check/generator/CheckGenerator.xtend | 12 +- .../LfNormalizingFileSystemAccess.java | 126 ++++++++++++++++++ .../generator/CheckCfgGenerator.xtend | 5 +- 3 files changed, 137 insertions(+), 6 deletions(-) create mode 100644 com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/generator/LfNormalizingFileSystemAccess.java diff --git a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/generator/CheckGenerator.xtend b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/generator/CheckGenerator.xtend index be2ed319b4..083ddc9b25 100644 --- a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/generator/CheckGenerator.xtend +++ b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/generator/CheckGenerator.xtend @@ -16,6 +16,7 @@ import com.google.inject.Inject import org.eclipse.emf.ecore.resource.Resource import org.eclipse.xtext.generator.AbstractFileSystemAccess import org.eclipse.xtext.generator.IFileSystemAccess +import org.eclipse.xtext.generator.IFileSystemAccess2 import org.eclipse.xtext.xbase.compiler.JvmModelGenerator import static org.eclipse.xtext.xbase.lib.IteratorExtensions.* @@ -36,15 +37,16 @@ class CheckGenerator extends JvmModelGenerator { @Inject ICheckGeneratorConfigProvider generatorConfigProvider; override void doGenerate(Resource resource, IFileSystemAccess fsa) { - super.doGenerate(resource, fsa); // Generate validator, catalog, and preference initializer from inferred Jvm models. + val lfFsa = new LfNormalizingFileSystemAccess(fsa as IFileSystemAccess2) + super.doGenerate(resource, lfFsa); // Generate validator, catalog, and preference initializer from inferred Jvm models. val config = generatorConfigProvider.get(resource?.URI); for (catalog : toIterable(resource.allContents).filter(typeof(CheckCatalog))) { - fsa.generateFile(catalog.issueCodesFilePath, catalog.compileIssueCodes) - fsa.generateFile(catalog.standaloneSetupPath, catalog.compileStandaloneSetup) + lfFsa.generateFile(catalog.issueCodesFilePath, catalog.compileIssueCodes) + lfFsa.generateFile(catalog.standaloneSetupPath, catalog.compileStandaloneSetup) // change output path for service registry - fsa.generateFile( + lfFsa.generateFile( CheckUtil::serviceRegistryClassName, CheckGeneratorConstants::CHECK_REGISTRY_OUTPUT, catalog.generateServiceRegistry(CheckUtil::serviceRegistryClassName, fsa) @@ -52,7 +54,7 @@ class CheckGenerator extends JvmModelGenerator { // generate documentation for SCA-checks only if(config !== null && (config.doGenerateDocumentationForAllChecks || !config.generateLanguageInternalChecks)){ // change output path for html files to docs/ - fsa.generateFile(catalog.docFileName, CheckGeneratorConstants::CHECK_DOC_OUTPUT, catalog.compileDoc) + lfFsa.generateFile(catalog.docFileName, CheckGeneratorConstants::CHECK_DOC_OUTPUT, catalog.compileDoc) } } } diff --git a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/generator/LfNormalizingFileSystemAccess.java b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/generator/LfNormalizingFileSystemAccess.java new file mode 100644 index 0000000000..53dbd1f2af --- /dev/null +++ b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/generator/LfNormalizingFileSystemAccess.java @@ -0,0 +1,126 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.check.generator; + +import java.io.InputStream; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.xtext.generator.IFileSystemAccess2; + +import com.google.common.base.Preconditions; + + +/** + * A delegating {@link IFileSystemAccess2} that normalizes line endings to LF ({@code \n}) + * before writing content. This ensures generated files are platform-independent regardless + * of the OS on which the build runs. + * + *

Implements {@link IFileSystemAccess2} so that {@code instanceof} checks in the framework + * (e.g., in {@code JvmModelGenerator}) continue to work and no behavior is lost.

+ */ +public class LfNormalizingFileSystemAccess implements IFileSystemAccess2 { + + private final IFileSystemAccess2 delegate; + + /** + * Wraps the given delegate. Callers that hold the weaker {@link org.eclipse.xtext.generator.IFileSystemAccess} + * (e.g. from Xtext's {@code Generator2#doGenerate(Resource, IFileSystemAccess)}) must cast at the + * call site — every default Xtext FSA implementation is also an {@link IFileSystemAccess2}. + * + * @param delegate the delegate to wrap, must not be {@code null} + */ + public LfNormalizingFileSystemAccess(final IFileSystemAccess2 delegate) { + this.delegate = Preconditions.checkNotNull(delegate); + } + + @Override + public void generateFile(final String fileName, final CharSequence contents) { + delegate.generateFile(fileName, normalizeLineEndings(contents)); + } + + @Override + public void generateFile(final String fileName, final String outputConfigName, final CharSequence contents) { + delegate.generateFile(fileName, outputConfigName, normalizeLineEndings(contents)); + } + + @Override + public void deleteFile(final String fileName) { + delegate.deleteFile(fileName); + } + + @Override + public void generateFile(final String fileName, final InputStream content) { + delegate.generateFile(fileName, content); + } + + @Override + public void generateFile(final String fileName, final String outputConfigName, final InputStream content) { + delegate.generateFile(fileName, outputConfigName, content); + } + + @Override + public URI getURI(final String fileName, final String outputConfigName) { + return delegate.getURI(fileName, outputConfigName); + } + + @Override + public URI getURI(final String fileName) { + return delegate.getURI(fileName); + } + + @Override + public void deleteFile(final String fileName, final String outputConfigName) { + delegate.deleteFile(fileName, outputConfigName); + } + + @Override + public InputStream readBinaryFile(final String fileName, final String outputConfigName) { + return delegate.readBinaryFile(fileName, outputConfigName); + } + + @Override + public InputStream readBinaryFile(final String fileName) { + return delegate.readBinaryFile(fileName); + } + + @Override + public CharSequence readTextFile(final String fileName, final String outputConfigName) { + return delegate.readTextFile(fileName, outputConfigName); + } + + @Override + public CharSequence readTextFile(final String fileName) { + return delegate.readTextFile(fileName); + } + + @Override + public boolean isFile(final String path, final String outputConfigurationName) { + return delegate.isFile(path, outputConfigurationName); + } + + @Override + public boolean isFile(final String path) { + return delegate.isFile(path); + } + + private static CharSequence normalizeLineEndings(final CharSequence content) { + if (content == null) { + return null; + } + String text = content.toString(); + if (text.indexOf('\r') < 0) { + return content; + } + return text.replace("\r\n", "\n").replace("\r", "\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + } + +} diff --git a/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/generator/CheckCfgGenerator.xtend b/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/generator/CheckCfgGenerator.xtend index 9b305ef409..e7a49aa53e 100644 --- a/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/generator/CheckCfgGenerator.xtend +++ b/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/generator/CheckCfgGenerator.xtend @@ -10,12 +10,14 @@ *******************************************************************************/ package com.avaloq.tools.ddk.checkcfg.generator +import com.avaloq.tools.ddk.check.generator.LfNormalizingFileSystemAccess import com.avaloq.tools.ddk.check.runtime.configuration.ICheckConfigurationStoreService import com.avaloq.tools.ddk.checkcfg.checkcfg.CheckConfiguration import com.google.inject.Inject import org.eclipse.emf.ecore.resource.Resource import org.eclipse.xtext.generator.AbstractFileSystemAccess import org.eclipse.xtext.generator.IFileSystemAccess +import org.eclipse.xtext.generator.IFileSystemAccess2 import org.eclipse.xtext.generator.IGenerator import static org.eclipse.xtext.xbase.lib.IteratorExtensions.* @@ -37,8 +39,9 @@ class CheckCfgGenerator implements IGenerator { if (fsa instanceof AbstractFileSystemAccess) { fsa.setOutputPath(outputPath) } + val lfFsa = new LfNormalizingFileSystemAccess(fsa as IFileSystemAccess2) for (configuration:toIterable(resource.allContents).filter(typeof(CheckConfiguration))) { - fsa.generateFile(configuration.fileName, configuration.compile) + lfFsa.generateFile(configuration.fileName, configuration.compile) } }