Skip to content

Commit d4ccf02

Browse files
authored
1 parent f2d2c2f commit d4ccf02

2 files changed

Lines changed: 162 additions & 0 deletions

File tree

playground/index.html

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,29 @@
204204
</div>
205205
</div>
206206

207+
<div class="relative hidden" data-playground-target="autofixUnsafeWrapper">
208+
<button
209+
data-action="click->playground#autofixUnsafeEditor"
210+
data-playground-target="autofixUnsafeButton"
211+
id="autofix-unsafe-editor"
212+
class="text-gray-900 dark:text-gray-100 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-md px-3 py-1.5 text-sm font-medium"
213+
>
214+
<i class="fas fa-triangle-exclamation w-4"></i>
215+
<i
216+
class="fas fa-circle-check w-4 text-green-600 hidden ease-in duration-300"
217+
></i>
218+
219+
<span class="ml-1">Autofix Unsafe</span>
220+
</button>
221+
<div
222+
data-playground-target="autofixUnsafeTooltip"
223+
class="hidden absolute top-full left-1/2 transform -translate-x-1/2 mt-2 px-2 py-1 text-xs text-white bg-black rounded-md whitespace-nowrap z-50"
224+
>
225+
Autocorrect unsafe Herb Linter offenses
226+
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-4 border-r-4 border-b-4 border-transparent border-b-black"></div>
227+
</div>
228+
</div>
229+
207230
<div class="relative">
208231
<button
209232
data-action="click->playground#formatEditor"

playground/src/controllers/playground_controller.js

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ export default class extends Controller {
9090
"formatTooltip",
9191
"autofixButton",
9292
"autofixTooltip",
93+
"autofixUnsafeWrapper",
94+
"autofixUnsafeButton",
95+
"autofixUnsafeTooltip",
9396
"printerViewer",
9497
"printerOutput",
9598
"printerVerification",
@@ -204,6 +207,7 @@ export default class extends Controller {
204207
this.setupThemeListener()
205208
this.setupTooltip()
206209
this.setupAutofixTooltip()
210+
this.setupAutofixUnsafeTooltip()
207211
this.setupShareTooltip()
208212
this.setupGitHubTooltip()
209213
this.setupCopyTooltip()
@@ -265,6 +269,7 @@ export default class extends Controller {
265269
window.removeEventListener("popstate", this.handlePopState)
266270
this.removeTooltip()
267271
this.removeAutofixTooltip()
272+
this.removeAutofixUnsafeTooltip()
268273
this.removeShareTooltip()
269274
this.removeGitHubTooltip()
270275
this.removeCopyTooltip()
@@ -785,6 +790,60 @@ export default class extends Controller {
785790
}
786791
}
787792

793+
async autofixUnsafeEditor(event) {
794+
if (this.isRubyMode) return
795+
796+
const button = this.getClosestButton(event.target)
797+
798+
if (button.disabled) {
799+
return
800+
}
801+
802+
const warningIcon = button.querySelector(".fa-triangle-exclamation")
803+
const checkIcon = button.querySelector(".fa-circle-check")
804+
805+
try {
806+
const value = this.editor ? this.editor.getValue() : this.inputTarget.value
807+
const linter = new Linter(Herb)
808+
const result = linter.autofix(value, undefined, undefined, { includeUnsafe: true })
809+
810+
if (result && typeof result === "object" && "source" in result) {
811+
const fixedCount = Array.isArray(result.fixed) ? result.fixed.length : 0
812+
813+
if (fixedCount > 0 && typeof result.source === "string") {
814+
if (this.editor) {
815+
this.editor.setValue(result.source)
816+
} else {
817+
this.inputTarget.value = result.source
818+
}
819+
820+
if (warningIcon && checkIcon) {
821+
warningIcon.classList.add("hidden")
822+
checkIcon.classList.remove("hidden")
823+
checkIcon.style.display = ""
824+
825+
setTimeout(() => {
826+
this.resetAutofixUnsafeButtonIcons()
827+
}, 1000)
828+
}
829+
830+
const offensesLabel = fixedCount === 1 ? "offense" : "offenses"
831+
this.showTemporaryMessage(`Autofixed ${fixedCount} unsafe linter ${offensesLabel}`, "success")
832+
833+
await this.analyze()
834+
this.resetAutofixUnsafeButtonIcons()
835+
} else {
836+
this.showTemporaryMessage("No unsafe autocorrectable linter offenses found", "info")
837+
}
838+
} else {
839+
this.showTemporaryMessage("Failed to autofix unsafe linter offenses", "error")
840+
}
841+
} catch (error) {
842+
console.error("Autofix unsafe error:", error)
843+
this.showTemporaryMessage("Failed to autofix unsafe linter offenses", "error")
844+
}
845+
}
846+
788847
async analyze() {
789848
this.updateURL()
790849

@@ -1036,6 +1095,20 @@ export default class extends Controller {
10361095
}
10371096
}
10381097

1098+
if (this.hasAutofixUnsafeWrapperTarget) {
1099+
const hasParserErrors = result.parseResult ? result.parseResult.recursiveErrors().length > 0 : false
1100+
const hasUnsafeOffenses = !!(result.lintResult && Array.isArray(result.lintResult.offenses) &&
1101+
result.lintResult.offenses.some(offense => offense.autofixContext && offense.autofixContext.unsafe === true))
1102+
1103+
if (hasParserErrors || !hasUnsafeOffenses) {
1104+
this.autofixUnsafeWrapperTarget.classList.add('hidden')
1105+
} else {
1106+
this.autofixUnsafeWrapperTarget.classList.remove('hidden')
1107+
this.enableAutofixUnsafeButton()
1108+
this.updateAutofixUnsafeTooltipText('Autocorrect unsafe Herb Linter offenses')
1109+
}
1110+
}
1111+
10391112
if (this.hasRubyViewerTarget) {
10401113
this.rubyViewerTarget.classList.add("language-ruby")
10411114
this.rubyViewerTarget.textContent = result.ruby
@@ -1717,6 +1790,72 @@ export default class extends Controller {
17171790
}
17181791
}
17191792

1793+
setupAutofixUnsafeTooltip() {
1794+
if (this.hasAutofixUnsafeTooltipTarget) {
1795+
this.autofixUnsafeButtonTarget.addEventListener('mouseenter', this.showAutofixUnsafeTooltip)
1796+
this.autofixUnsafeButtonTarget.addEventListener('mouseleave', this.hideAutofixUnsafeTooltip)
1797+
}
1798+
}
1799+
1800+
removeAutofixUnsafeTooltip() {
1801+
if (this.hasAutofixUnsafeTooltipTarget) {
1802+
this.autofixUnsafeButtonTarget.removeEventListener('mouseenter', this.showAutofixUnsafeTooltip)
1803+
this.autofixUnsafeButtonTarget.removeEventListener('mouseleave', this.hideAutofixUnsafeTooltip)
1804+
1805+
this.hideAutofixUnsafeTooltip()
1806+
}
1807+
}
1808+
1809+
showAutofixUnsafeTooltip = () => {
1810+
if (this.hasAutofixUnsafeTooltipTarget) {
1811+
this.autofixUnsafeTooltipTarget.classList.remove('hidden')
1812+
}
1813+
}
1814+
1815+
hideAutofixUnsafeTooltip = () => {
1816+
if (this.hasAutofixUnsafeTooltipTarget) {
1817+
this.autofixUnsafeTooltipTarget.classList.add('hidden')
1818+
}
1819+
}
1820+
1821+
updateAutofixUnsafeTooltipText(text) {
1822+
if (this.hasAutofixUnsafeTooltipTarget) {
1823+
const textNode = this.autofixUnsafeTooltipTarget.firstChild
1824+
if (textNode && textNode.nodeType === Node.TEXT_NODE) {
1825+
textNode.textContent = text
1826+
}
1827+
}
1828+
}
1829+
1830+
enableAutofixUnsafeButton() {
1831+
this.autofixUnsafeButtonTarget.disabled = false
1832+
this.autofixUnsafeButtonTarget.classList.remove('opacity-50', 'cursor-not-allowed')
1833+
this.autofixUnsafeButtonTarget.classList.add('hover:bg-gray-200', 'dark:hover:bg-gray-700')
1834+
}
1835+
1836+
disableAutofixUnsafeButton() {
1837+
this.autofixUnsafeButtonTarget.disabled = true
1838+
this.autofixUnsafeButtonTarget.classList.add('opacity-50', 'cursor-not-allowed')
1839+
this.autofixUnsafeButtonTarget.classList.remove('hover:bg-gray-200', 'dark:hover:bg-gray-700')
1840+
this.resetAutofixUnsafeButtonIcons()
1841+
}
1842+
1843+
resetAutofixUnsafeButtonIcons() {
1844+
if (!this.hasAutofixUnsafeButtonTarget) return
1845+
1846+
const warningIcon = this.autofixUnsafeButtonTarget.querySelector(".fa-triangle-exclamation")
1847+
const checkIcon = this.autofixUnsafeButtonTarget.querySelector(".fa-circle-check")
1848+
1849+
if (warningIcon) {
1850+
warningIcon.classList.remove("hidden")
1851+
}
1852+
1853+
if (checkIcon) {
1854+
checkIcon.classList.add("hidden")
1855+
checkIcon.style.display = ""
1856+
}
1857+
}
1858+
17201859
setupShareTooltip() {
17211860
if (this.hasShareButtonTarget && this.hasShareTooltipTarget) {
17221861
this.shareButtonTarget.addEventListener('mouseenter', this.showShareTooltip)

0 commit comments

Comments
 (0)