make "invalid @apply" quick fix work in mixed-language documents

master
Brad Cornes 2020-06-18 19:58:54 +01:00
parent a6a8f7e536
commit 6add64c19b
1 changed files with 89 additions and 63 deletions

View File

@ -6,7 +6,6 @@ import {
TextEdit, TextEdit,
} from 'vscode-languageserver' } from 'vscode-languageserver'
import { State } from '../../util/state' import { State } from '../../util/state'
import { findLast } from '../../util/find'
import { isWithinRange } from '../../util/isWithinRange' import { isWithinRange } from '../../util/isWithinRange'
import { getClassNameParts } from '../../util/getClassNameAtPosition' import { getClassNameParts } from '../../util/getClassNameAtPosition'
const dlv = require('dlv') const dlv = require('dlv')
@ -31,6 +30,9 @@ import {
} from '../diagnostics/types' } from '../diagnostics/types'
import { flatten, dedupeBy } from '../../../util/array' import { flatten, dedupeBy } from '../../../util/array'
import { joinWithAnd } from '../../util/joinWithAnd' import { joinWithAnd } from '../../util/joinWithAnd'
import { getLanguageBoundaries } from '../../util/getLanguageBoundaries'
import { isCssDoc } from '../../util/css'
import { absoluteRange } from '../../util/absoluteRange'
async function getDiagnosticsFromCodeActionParams( async function getDiagnosticsFromCodeActionParams(
state: State, state: State,
@ -210,6 +212,8 @@ async function provideInvalidApplyCodeActions(
): Promise<CodeAction[]> { ): Promise<CodeAction[]> {
let document = state.editor.documents.get(params.textDocument.uri) let document = state.editor.documents.get(params.textDocument.uri)
let documentText = document.getText() let documentText = document.getText()
let cssRange: Range
let cssText = documentText
const { postcss } = state.modules const { postcss } = state.modules
let change: TextEdit let change: TextEdit
@ -217,54 +221,67 @@ async function provideInvalidApplyCodeActions(
/\s+/ /\s+/
).length ).length
await postcss([ if (!isCssDoc(state, document)) {
postcss.plugin('', (_options = {}) => { let languageBoundaries = getLanguageBoundaries(state, document)
return (root) => { if (!languageBoundaries) return []
root.walkRules((rule) => { cssRange = languageBoundaries.css.find((range) =>
if (change) return false isWithinRange(diagnostic.range.start, range)
)
if (!cssRange) return []
cssText = document.getText(cssRange)
}
rule.walkAtRules('apply', (atRule) => { try {
let { start, end } = atRule.source await postcss([
let range: Range = { postcss.plugin('', (_options = {}) => {
start: { return (root) => {
line: start.line - 1, root.walkRules((rule) => {
character: start.column - 1, if (change) return false
},
end: {
line: end.line - 1,
character: end.column - 1,
},
}
if (!isWithinRange(diagnostic.range.start, range)) { rule.walkAtRules('apply', (atRule) => {
// keep looking let { start, end } = atRule.source
return true let atRuleRange: Range = {
} start: {
line: start.line - 1,
character: start.column - 1,
},
end: {
line: end.line - 1,
character: end.column - 1,
},
}
if (cssRange) {
atRuleRange = absoluteRange(atRuleRange, cssRange)
}
let className = document.getText(diagnostic.range) if (!isWithinRange(diagnostic.range.start, atRuleRange)) {
let ast = classNameToAst( // keep looking
state, return true
className, }
rule.selector,
diagnostic.className.classList.important
)
if (!ast) { let className = diagnostic.className.className
return false let ast = classNameToAst(
} state,
className,
rule.selector,
diagnostic.className.classList.important
)
rule.after(ast.nodes) if (!ast) {
let insertedRule = rule.next() return false
}
if (totalClassNamesInClassList === 1) { rule.after(ast.nodes)
atRule.remove() let insertedRule = rule.next()
}
let outputIndent: string if (totalClassNamesInClassList === 1) {
let documentIndent = detectIndent(documentText) atRule.remove()
}
change = { let outputIndent: string
range: { let documentIndent = detectIndent(documentText)
let ruleRange: Range = {
start: { start: {
line: rule.source.start.line - 1, line: rule.source.start.line - 1,
character: rule.source.start.column - 1, character: rule.source.start.column - 1,
@ -273,30 +290,39 @@ async function provideInvalidApplyCodeActions(
line: rule.source.end.line - 1, line: rule.source.end.line - 1,
character: rule.source.end.column, character: rule.source.end.column,
}, },
}, }
newText: if (cssRange) {
rule.toString() + ruleRange = absoluteRange(ruleRange, cssRange)
(insertedRule.raws.before || '\n\n') + }
insertedRule
.toString()
.replace(/\n\s*\n/g, '\n')
.replace(/(@apply [^;\n]+)$/gm, '$1;')
.replace(/([^\s^]){$/gm, '$1 {')
.replace(/^\s+/gm, (m: string) => {
if (typeof outputIndent === 'undefined') outputIndent = m
return m.replace(
new RegExp(outputIndent, 'g'),
documentIndent.indent
)
}),
}
return false change = {
range: ruleRange,
newText:
rule.toString() +
(insertedRule.raws.before || '\n\n') +
insertedRule
.toString()
.replace(/\n\s*\n/g, '\n')
.replace(/(@apply [^;\n]+)$/gm, '$1;')
.replace(/([^\s^]){$/gm, '$1 {')
.replace(/^\s+/gm, (m: string) => {
if (typeof outputIndent === 'undefined') outputIndent = m
return m.replace(
new RegExp(outputIndent, 'g'),
documentIndent.indent
)
}),
}
return false
})
}) })
}) }
} }),
}), ]).process(cssText, { from: undefined })
]).process(documentText, { from: undefined }) } catch (_) {
return []
}
if (!change) { if (!change) {
return [] return []