Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
## [Unreleased]
### Added
- Add support for AsciiDoc formatting via `adocfmt`. ([#2960](https://github.com/diffplug/spotless/pull/2960))
- `flexmark` step now supports arbitrary formatter options via a `formatterOptions` map. ([#2968](https://github.com/diffplug/spotless/pull/2968))
### Fixed
- Support `ktfmt` 0.63 and use its new builder API for formatting options to better avoid future breaking changes.
### Changes
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021-2025 DiffPlug
* Copyright 2021-2026 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -27,6 +27,7 @@
import com.vladsch.flexmark.parser.PegdownExtensions;
import com.vladsch.flexmark.profile.pegdown.PegdownOptionsAdapter;
import com.vladsch.flexmark.util.ast.Document;
import com.vladsch.flexmark.util.data.DataKey;
import com.vladsch.flexmark.util.data.MutableDataHolder;
import com.vladsch.flexmark.util.data.MutableDataSet;
import com.vladsch.flexmark.util.misc.Extension;
Expand Down Expand Up @@ -73,7 +74,7 @@ public FlexmarkFormatterFunc(FlexmarkConfig config) {
final ParserEmulationProfile emulationProfile = ParserEmulationProfile.valueOf(config.getEmulationProfile());

final MutableDataHolder parserOptions = createParserOptions(emulationProfile, config);
final MutableDataHolder formatterOptions = createFormatterOptions(parserOptions, emulationProfile);
final MutableDataHolder formatterOptions = createFormatterOptions(parserOptions, emulationProfile, config);

parser = Parser.builder(parserOptions).build();
formatter = Formatter.builder(formatterOptions).build();
Expand Down Expand Up @@ -145,20 +146,74 @@ private static Extension[] buildExtensions(List<String> config) {

/**
* Creates the formatter options, copies the parser extensions and changes defaults that make sense for a formatter.
* Arbitrary flexmark formatter options can be set via {@link FlexmarkConfig#getFormatterOptions()}: each key is a
* SCREAMING_SNAKE_CASE name (e.g. {@code RIGHT_MARGIN}) matching the corresponding static {@link DataKey} field on
* {@link Formatter}. An unrecognised key fails the build.
* See: https://github.com/vsch/flexmark-java/wiki/Markdown-Formatter#options
*
* @param parserOptions the options used for the parser
* @param emulationProfile the emulation profile (or flavor of markdown) the formatter should use
* @param config the flexmark config, including any additional formatter options
* @return the created formatter options
*/
private static MutableDataHolder createFormatterOptions(MutableDataHolder parserOptions,
ParserEmulationProfile emulationProfile) {
ParserEmulationProfile emulationProfile, FlexmarkConfig config) {
final MutableDataHolder formatterOptions = new MutableDataSet();
formatterOptions.set(Parser.EXTENSIONS, Parser.EXTENSIONS.get(parserOptions));
formatterOptions.set(Formatter.FORMATTER_EMULATION_PROFILE, emulationProfile);
applyFlexmarkOptions(formatterOptions, config.getFormatterOptions());
return formatterOptions;
}

/**
* Applies arbitrary formatter options from the config map to the given data holder.
* Each key is a SCREAMING_SNAKE_CASE name (e.g. {@code RIGHT_MARGIN}) matching the corresponding static
* {@link DataKey} field on {@link Formatter}. Supported value types are {@link Integer}, {@link Boolean}, and
* {@link String}; the type is inferred from the DataKey's default value. An unknown key or unsupported type
* throws {@link IllegalArgumentException}, which fails the build.
*/
private static void applyFlexmarkOptions(MutableDataHolder options, Map<String, String> formatterOptions) {
if (formatterOptions.isEmpty()) {
return;
}
MutableDataSet defaults = new MutableDataSet();
for (Map.Entry<String, String> entry : formatterOptions.entrySet()) {
String fieldName = entry.getKey();
String rawValue = entry.getValue();
Field field;
try {
field = Formatter.class.getField(fieldName);
} catch (NoSuchFieldException e) {
throw new IllegalArgumentException(
"Unknown flexmark formatter option: no field Formatter." + fieldName
+ ". See https://github.com/vsch/flexmark-java/wiki/Markdown-Formatter#options");
}
DataKey<?> dataKey;
try {
dataKey = (DataKey<?>) field.get(null);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException("Cannot access field Formatter." + fieldName, e);
}
setOption(options, dataKey, dataKey.getDefaultValue(defaults), rawValue, fieldName);
}
}

@SuppressWarnings({"unchecked", "rawtypes"})
private static void setOption(MutableDataHolder options, DataKey dataKey, Object defaultValue, String rawValue,
String fieldName) {
if (defaultValue instanceof Integer) {
options.set(dataKey, Integer.parseInt(rawValue));
} else if (defaultValue instanceof Boolean) {
options.set(dataKey, Boolean.parseBoolean(rawValue));
} else if (defaultValue instanceof String) {
options.set(dataKey, rawValue);
} else {
throw new IllegalArgumentException("Unsupported type for flexmark option '" + fieldName + "': "
+ (defaultValue == null ? "null" : defaultValue.getClass().getName())
+ ". Only Integer, Boolean, and String options are supported.");
}
}

@Override
public String apply(String input) throws Exception {
final Document parsedMarkdown = parser.parse(input);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2025 DiffPlug
* Copyright 2025-2026 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -19,18 +19,22 @@
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class FlexmarkConfig implements Serializable {
@Serial
private static final long serialVersionUID = 1L;

/**
* The emulation profile is used by both the parser and the formatter and generally determines the markdown flavor.
* COMMONMARK is the default defined by flexmark-java.
* COMMONMARK is the default defined by flexmark-java. For convenience, this can also be set via
* {@code formatterOptions} with the key {@code FORMATTER_EMULATION_PROFILE}.
*/
private String emulationProfile = "COMMONMARK";
private List<String> pegdownExtensions = List.of("ALL");
private List<String> extensions = new ArrayList<>();
private Map<String, String> formatterOptions = new TreeMap<>();

public String getEmulationProfile() {
return emulationProfile;
Expand All @@ -56,4 +60,12 @@ public void setExtensions(List<String> extensions) {
this.extensions = extensions;
}

public Map<String, String> getFormatterOptions() {
return formatterOptions;
}

public void setFormatterOptions(Map<String, String> formatterOptions) {
this.formatterOptions = new TreeMap<>(formatterOptions);
}

}
1 change: 1 addition & 0 deletions plugin-gradle/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (

### Added
- Add support for AsciiDoc formatting via `adocfmt`. ([#2960](https://github.com/diffplug/spotless/pull/2960))
- `flexmark()` step now supports arbitrary formatter options via the `formatterOptions` map. ([#2968](https://github.com/diffplug/spotless/pull/2968))
### Fixed
- Prevent build caches from interfering when executing under the `-PspotlessIdeHook` mode. ([#2365](https://github.com/diffplug/spotless/issues/2365))
### Changes
Expand Down
3 changes: 3 additions & 0 deletions plugin-gradle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,8 @@ You can change the `emulationProfile` to one of the other [supported profiles](h
The `pegdownExtensions` can be configured as a list of [constants](https://github.com/vsch/flexmark-java/blob/master/flexmark/src/main/java/com/vladsch/flexmark/parser/PegdownExtensions.java) or as a custom bitset as an integer.
Any other `extension` can be configured using either the simple name as shown in the example or using a full-qualified class name.

Arbitrary formatter options from the [flexmark-java Markdown Formatter](https://github.com/vsch/flexmark-java/wiki/Markdown-Formatter#options) can be set via `formatterOptions`. Each key is a `SCREAMING_SNAKE_CASE` constant from [`com.vladsch.flexmark.formatter.Formatter`](https://github.com/vsch/flexmark-java/blob/master/flexmark/src/main/java/com/vladsch/flexmark/formatter/Formatter.java) (e.g. `RIGHT_MARGIN`). Supported value types are `Integer`, `Boolean`, and `String`.

To apply flexmark to all of the `.md` files in your project, use this snippet:

```gradle
Expand All @@ -805,6 +807,7 @@ spotless {
.emulationProfile('COMMONMARK') // optional
.pegdownExtensions('ALL') // optional
.extensions('YamlFrontMatter') // optional
.formatterOptions(['RIGHT_MARGIN': '120']) // optional
}
}
```
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 DiffPlug
* Copyright 2023-2026 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,6 +16,7 @@
package com.diffplug.gradle.spotless;

import java.util.List;
import java.util.Map;
import java.util.Objects;

import javax.inject.Inject;
Expand Down Expand Up @@ -79,6 +80,11 @@ public FlexmarkFormatterConfig extensions(String... extensions) {
return this;
}

public FlexmarkFormatterConfig formatterOptions(Map<String, String> formatterOptions) {
this.config.setFormatterOptions(formatterOptions);
return this;
}

private FormatterStep createStep() {
return FlexmarkStep.create(this.version, provisioner(), config);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 DiffPlug
* Copyright 2023-2026 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -43,6 +43,27 @@ void integrationSimpleExtensions() throws IOException {
assertFile("markdown_test.md").sameAsResource("markdown/flexmark/FlexmarkFormatted.md");
}

@Test
void integrationFormatterOptions() throws IOException {
setFile("build.gradle").toLines(
"plugins {",
" id 'java'",
" id 'com.diffplug.spotless'",
"}",
"repositories { mavenCentral() }",
"spotless {",
" flexmark {",
" target '*.md'",
" flexmark()",
" .extensions('YamlFrontMatter')",
" .formatterOptions(['RIGHT_MARGIN': '100'])",
" }",
"}");
setFile("markdown_test.md").toResource("markdown/flexmark/FlexmarkOptionsUnformatted.md");
gradleRunner().withArguments("spotlessApply").build();
assertFile("markdown_test.md").sameAsResource("markdown/flexmark/FlexmarkOptionsFormatted.md");
}

@Test
void integrationComplex() throws IOException {
setFile("build.gradle").toLines(
Expand Down
1 change: 1 addition & 0 deletions plugin-maven/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (

### Added
- Add support for AsciiDoc formatting via `adocfmt`. ([#2960](https://github.com/diffplug/spotless/pull/2960))
- `<flexmark>` step now supports arbitrary formatter options via `<formatterOptions>`. ([#2968](https://github.com/diffplug/spotless/pull/2968))

## [3.6.0] - 2026-05-27
### Added
Expand Down
5 changes: 5 additions & 0 deletions plugin-maven/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -878,11 +878,16 @@ You can change the `emulationProfile` to one of the other [supported profiles](h
The `pegdownExtensions` can be configured as a comma-seperated list of [constants](https://github.com/vsch/flexmark-java/blob/master/flexmark/src/main/java/com/vladsch/flexmark/parser/PegdownExtensions.java) or as a custom bitset as an integer.
Any other `extension` can be configured using either the simple name as shown in the example or using a full-qualified class name.

Arbitrary formatter options from the [flexmark-java Markdown Formatter](https://github.com/vsch/flexmark-java/wiki/Markdown-Formatter#options) can be set via `formatterOptions`. Each key is a camelCase version of the corresponding `SCREAMING_SNAKE_CASE` constant on [`com.vladsch.flexmark.formatter.Formatter`](https://github.com/vsch/flexmark-java/blob/master/flexmark/src/main/java/com/vladsch/flexmark/formatter/Formatter.java) (e.g. `rightMargin` maps to `Formatter.RIGHT_MARGIN`). Supported value types are `Integer`, `Boolean`, and `String`.

```xml
<flexmark>
<emulationProfile>COMMONMARK</emulationProfile>
<pegdownExtensions>ALL,TOC</pegdownExtensions>
<extensions>YamlFrontMatter,Emoji</extensions>
<formatterOptions>
<rightMargin>120</rightMargin>
</formatterOptions>
</flexmark>
```

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021-2025 DiffPlug
* Copyright 2021-2026 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,7 +15,10 @@
*/
package com.diffplug.spotless.maven.markdown;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.apache.maven.plugins.annotations.Parameter;

Expand All @@ -36,6 +39,8 @@ public class Flexmark implements FormatterStepFactory {
private String pegdownExtensions;
@Parameter
private String extensions;
@Parameter
private Map<String, String> formatterOptions;

@Override
public FormatterStep newFormatterStep(FormatterStepConfig config) {
Expand All @@ -50,6 +55,17 @@ public FormatterStep newFormatterStep(FormatterStepConfig config) {
if (this.extensions != null) {
flexmarkConfig.setExtensions(List.of(this.extensions.split(",")));
}
if (this.formatterOptions != null) {
Map<String, String> converted = new LinkedHashMap<>();
for (Map.Entry<String, String> entry : this.formatterOptions.entrySet()) {
converted.put(camelToScreamingSnake(entry.getKey()), entry.getValue());
}
flexmarkConfig.setFormatterOptions(converted);
}
return FlexmarkStep.create(version, config.getProvisioner(), flexmarkConfig);
}

private static String camelToScreamingSnake(String camel) {
return camel.replaceAll("([A-Z])", "_$1").toUpperCase(Locale.ROOT);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021-2025 DiffPlug
* Copyright 2021-2026 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -30,6 +30,15 @@ public void testFlexmarkWithSimpleConfig() throws Exception {
assertFile("markdown_test.md").sameAsResource("markdown/flexmark/FlexmarkFormatted.md");
}

@Test
public void testFlexmarkWithOptions() throws Exception {
writePomWithMarkdownSteps("<flexmark><extensions>YamlFrontMatter</extensions><formatterOptions><rightMargin>100</rightMargin></formatterOptions></flexmark>");

setFile("markdown_test.md").toResource("markdown/flexmark/FlexmarkOptionsUnformatted.md");
mavenRunner().withArguments("spotless:apply").runNoError();
assertFile("markdown_test.md").sameAsResource("markdown/flexmark/FlexmarkOptionsFormatted.md");
}

@Test
public void testFlexmarkWithComplexConfig() throws Exception {
writePomWithMarkdownSteps("<flexmark><emulationProfile>COMMONMARK</emulationProfile><pegdownExtensions>0x0000FFFF</pegdownExtensions><extensions>com.vladsch.flexmark.ext.yaml.front.matter.YamlFrontMatterExtension</extensions></flexmark>");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
key: value
---

A short paragraph.

A long paragraph that needs wrapping because it exceeds the configured right margin of one hundred
characters total.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
key: value
---

A short paragraph.

A long paragraph that needs wrapping because it exceeds the configured right margin of one hundred characters total.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2025 DiffPlug
* Copyright 2016-2026 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,7 +15,10 @@
*/
package com.diffplug.spotless.markdown;

import static org.assertj.core.api.Assertions.assertThatThrownBy;

import java.util.List;
import java.util.Map;

import org.junit.jupiter.api.Test;

Expand All @@ -35,6 +38,29 @@ void behaviorOldest() {
"markdown/flexmark/FlexmarkFormatted.md");
}

@Test
void flexmarkOptionsRightMargin() {
FlexmarkConfig config = new FlexmarkConfig();
config.setExtensions(List.of("YamlFrontMatter"));
config.setFormatterOptions(Map.of("RIGHT_MARGIN", "100"));
StepHarness.forStep(FlexmarkStep.create(TestProvisioner.mavenCentral(), config))
.testResource(
"markdown/flexmark/FlexmarkOptionsUnformatted.md",
"markdown/flexmark/FlexmarkOptionsFormatted.md");
}

@Test
void flexmarkOptionsUnknownKeyFails() {
FlexmarkConfig config = new FlexmarkConfig();
config.setFormatterOptions(Map.of("NON_EXISTENT_OPTION", "value"));
assertThatThrownBy(() -> StepHarness.forStep(FlexmarkStep.create(TestProvisioner.mavenCentral(), config))
.test("text\n", "text\n"))
.hasRootCauseInstanceOf(IllegalArgumentException.class)
.hasRootCauseMessage(
"Unknown flexmark formatter option: no field Formatter.NON_EXISTENT_OPTION."
+ " See https://github.com/vsch/flexmark-java/wiki/Markdown-Formatter#options");
}

@Test
void behaviorLatest() {
FlexmarkConfig config = new FlexmarkConfig();
Expand Down
Loading