• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pmd / pmd / 4553

25 Apr 2025 06:55AM UTC coverage: 77.84% (+0.008%) from 77.832%
4553

push

github

adangel
[core] Support language dialects (#5438)

Merge pull request #5438 from Monits:lang-dialects

17661 of 23654 branches covered (74.66%)

Branch coverage included in aggregate %.

113 of 137 new or added lines in 12 files covered. (82.48%)

20 existing lines in 5 files now uncovered.

38710 of 48765 relevant lines covered (79.38%)

0.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.11
/pmd-core/src/main/java/net/sourceforge/pmd/lang/impl/SimpleDialectLanguageModuleBase.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.lang.impl;
6

7
import java.util.Arrays;
8
import java.util.HashSet;
9
import java.util.List;
10
import java.util.Set;
11
import java.util.function.Function;
12

13
import org.checkerframework.checker.nullness.qual.NonNull;
14

15
import net.sourceforge.pmd.annotation.Experimental;
16
import net.sourceforge.pmd.cpd.CpdCapableLanguage;
17
import net.sourceforge.pmd.cpd.CpdLexer;
18
import net.sourceforge.pmd.lang.AbstractPmdLanguageVersionHandler;
19
import net.sourceforge.pmd.lang.Language;
20
import net.sourceforge.pmd.lang.LanguageModuleBase;
21
import net.sourceforge.pmd.lang.LanguageProcessor;
22
import net.sourceforge.pmd.lang.LanguagePropertyBundle;
23
import net.sourceforge.pmd.lang.LanguageRegistry;
24
import net.sourceforge.pmd.lang.LanguageVersionHandler;
25
import net.sourceforge.pmd.lang.PmdCapableLanguage;
26
import net.sourceforge.pmd.lang.ast.Parser;
27
import net.sourceforge.pmd.lang.metrics.LanguageMetricsProvider;
28
import net.sourceforge.pmd.lang.metrics.Metric;
29
import net.sourceforge.pmd.lang.rule.xpath.impl.XPathHandler;
30
import net.sourceforge.pmd.properties.PropertyDescriptor;
31
import net.sourceforge.pmd.reporting.ViolationDecorator;
32
import net.sourceforge.pmd.reporting.ViolationSuppressor;
33
import net.sourceforge.pmd.util.CollectionUtil;
34
import net.sourceforge.pmd.util.designerbindings.DesignerBindings;
35

36
/**
37
 * The simplest implementation of a dialect, where only a {@link LanguageMetadata}
38
 * needs to be implemented. Everything gets delegated to the base language,
39
 * with all dialect extension applied.
40
 *
41
 * @author Juan Martín Sotuyo Dodero
42
 * @since 7.13.0
43
 * @experimental Since 7.13.0. See <a href="https://github.com/pmd/pmd/pull/5438">[core] Support language dialects #5438</a>.
44
 */
45
@Experimental
1✔
46
public class SimpleDialectLanguageModuleBase extends LanguageModuleBase implements PmdCapableLanguage, CpdCapableLanguage {
47

48
    private final Function<LanguagePropertyBundle, BasePmdDialectLanguageVersionHandler> handler;
49

50
    protected SimpleDialectLanguageModuleBase(DialectLanguageMetadata metadata) {
NEW
51
        this(metadata, new BasePmdDialectLanguageVersionHandler());
×
NEW
52
    }
×
53

54
    protected SimpleDialectLanguageModuleBase(DialectLanguageMetadata metadata, BasePmdDialectLanguageVersionHandler handler) {
55
        this(metadata, p -> handler);
1✔
56
    }
1✔
57

58
    protected SimpleDialectLanguageModuleBase(DialectLanguageMetadata metadata, Function<LanguagePropertyBundle, BasePmdDialectLanguageVersionHandler> makeHandler) {
59
        super(metadata);
1✔
60
        assert getBaseLanguageId() != null : "Language " + getId() + " is not a dialect of another language.";
1!
61

62
        this.handler = makeHandler;
1✔
63
    }
1✔
64

65
    private @NonNull Language getBaseLanguageFromRegistry(LanguageRegistry registry) {
66
        final Language baseLanguage = registry.getLanguageById(getBaseLanguageId());
1✔
67

68
        if (baseLanguage == null) {
1!
NEW
69
            throw new IllegalStateException(
×
NEW
70
                    "Language " + getId() + " has unsatisfied dependencies: "
×
NEW
71
                            + getBaseLanguageId() + " is not found in " + registry
×
72
            );
73
        }
74

75
        return baseLanguage;
1✔
76
    }
77

78
    /**
79
     * Creates a combined property bundle with all properties from the dialect and the base language.
80
     * To define dialect-specific properties to be added to this bundle, override {@link #newDialectPropertyBundle()}
81
     * @return A new set of properties
82
     */
83
    @Override
84
    public final LanguagePropertyBundle newPropertyBundle() {
85
        LanguagePropertyBundle baseBundle = getBaseLanguageFromRegistry(LanguageRegistry.PMD).newPropertyBundle();
1✔
86
        LanguagePropertyBundle dialectBundle = newDialectPropertyBundle();
1✔
87

88
        for (PropertyDescriptor<?> pd : baseBundle.getPropertyDescriptors()) {
1✔
89
            if (!dialectBundle.hasDescriptor(pd)) {
1✔
90
                dialectBundle.definePropertyDescriptor(pd);
1✔
91
            }
92
        }
1✔
93

94
        return dialectBundle;
1✔
95
    }
96

97
    protected @NonNull LanguagePropertyBundle newDialectPropertyBundle() {
98
        return new LanguagePropertyBundle(this);
1✔
99
    }
100

101
    @Override
102
    public final LanguageProcessor createProcessor(LanguagePropertyBundle bundle) {
103
        final PmdCapableLanguage baseLanguage = (PmdCapableLanguage) getBaseLanguageFromRegistry(LanguageRegistry.PMD);
1✔
104
        final BasePmdDialectLanguageVersionHandler dialectHandler = handler.apply(bundle);
1✔
105

106
        return new DialectLanguageProcessor(baseLanguage, dialectHandler, bundle);
1✔
107
    }
108

109
    @Override
110
    public CpdLexer createCpdLexer(LanguagePropertyBundle bundle) {
NEW
111
        final CpdCapableLanguage baseLanguage = (CpdCapableLanguage) getBaseLanguageFromRegistry(LanguageRegistry.CPD);
×
NEW
112
        return baseLanguage.createCpdLexer(bundle);
×
113
    }
114

115
    /**
116
     * A Language processor for dialects. It delegates everything to the base language, but extends
117
     * the {@link LanguageVersionHandler} with any dialect-specific options.
118
     */
119
    private static final class DialectLanguageProcessor extends BatchLanguageProcessor<LanguagePropertyBundle> {
120
        private final LanguageProcessor baseLanguageProcessor;
121
        private final LanguageVersionHandler combinedHandler;
122

123
        private DialectLanguageProcessor(PmdCapableLanguage baseLanguage, BasePmdDialectLanguageVersionHandler dialectHandler, LanguagePropertyBundle bundle) {
124
            super(bundle);
1✔
125
            this.baseLanguageProcessor = baseLanguage.createProcessor(bundle);
1✔
126
            this.combinedHandler = new SimpleDialectLanguageVersionHandler(baseLanguageProcessor.services(), dialectHandler);
1✔
127
        }
1✔
128

129
        @Override
130
        public @NonNull LanguageVersionHandler services() {
131
            return combinedHandler;
1✔
132
        }
133

134
        @Override
135
        public @NonNull AutoCloseable launchAnalysis(@NonNull AnalysisTask analysisTask) {
136
            return baseLanguageProcessor.launchAnalysis(analysisTask);
1✔
137
        }
138

139
        @Override
140
        public void close() throws Exception {
141
            super.close();
1✔
142
            baseLanguageProcessor.close();
1✔
143
        }
1✔
144
    }
145

146
    /**
147
     * A composite language version handler that merges a dialect's extension with the bae language.
148
     */
149
    private static class SimpleDialectLanguageVersionHandler extends AbstractPmdLanguageVersionHandler {
150

151
        private final LanguageVersionHandler baseLanguageVersionHandler;
152
        private final LanguageVersionHandler dialectLanguageVersionHandler;
153

154
        SimpleDialectLanguageVersionHandler(LanguageVersionHandler baseLanguageVersionHandler, LanguageVersionHandler dialectLanguageVersionHandler) {
1✔
155
            this.baseLanguageVersionHandler = baseLanguageVersionHandler;
1✔
156
            this.dialectLanguageVersionHandler = dialectLanguageVersionHandler;
1✔
157
        }
1✔
158

159
        @Override
160
        public XPathHandler getXPathHandler() {
161
            // Add dialect-specific XPath functions
162
            return baseLanguageVersionHandler.getXPathHandler()
1✔
163
                    .combine(dialectLanguageVersionHandler.getXPathHandler());
1✔
164
        }
165

166
        @Override
167
        public Parser getParser() {
168
            // Always the base language parser for full compatibility (same AST)
NEW
169
            return baseLanguageVersionHandler.getParser();
×
170
        }
171

172
        @Override
173
        public ViolationDecorator getViolationDecorator() {
NEW
174
            return ViolationDecorator.chain(
×
NEW
175
                    Arrays.asList(baseLanguageVersionHandler.getViolationDecorator(),
×
NEW
176
                            dialectLanguageVersionHandler.getViolationDecorator()));
×
177
        }
178

179
        @Override
180
        public List<ViolationSuppressor> getExtraViolationSuppressors() {
NEW
181
            return CollectionUtil.concatView(
×
NEW
182
                    baseLanguageVersionHandler.getExtraViolationSuppressors(),
×
NEW
183
                    dialectLanguageVersionHandler.getExtraViolationSuppressors());
×
184
        }
185

186
        @Override
187
        public LanguageMetricsProvider getLanguageMetricsProvider() {
188
            if (baseLanguageVersionHandler.getLanguageMetricsProvider() == null) {
1!
NEW
189
                return dialectLanguageVersionHandler.getLanguageMetricsProvider();
×
190
            }
191

192
            if (dialectLanguageVersionHandler.getLanguageMetricsProvider() == null) {
1!
NEW
193
                return baseLanguageVersionHandler.getLanguageMetricsProvider();
×
194
            }
195

196
            // merge metrics if both define any
197
            return () -> {
1✔
198
                Set<Metric<?, ?>> mergedSet = new HashSet<>();
1✔
199
                mergedSet.addAll(baseLanguageVersionHandler.getLanguageMetricsProvider().getMetrics());
1✔
200
                mergedSet.addAll(dialectLanguageVersionHandler.getLanguageMetricsProvider().getMetrics());
1✔
201
                return mergedSet;
1✔
202
            };
203
        }
204

205
        @Override
206
        public DesignerBindings getDesignerBindings() {
207
            // if the dialect set something it has priority
NEW
208
            if (DesignerBindings.DefaultDesignerBindings.getInstance().equals(dialectLanguageVersionHandler.getDesignerBindings())) {
×
NEW
209
                return baseLanguageVersionHandler.getDesignerBindings();
×
210
            }
211

NEW
212
            return dialectLanguageVersionHandler.getDesignerBindings();
×
213
        }
214
    }
215
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc