Skip to content

Commit

Permalink
Merge pull request #236 from marp-team/unknown-theme-diagnostics
Browse files Browse the repository at this point in the history
Add `unknown-theme` diagnostic
  • Loading branch information
yhatt authored May 14, 2021
2 parents cd26e93 + 213e303 commit 882d7cb
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 6 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

### Added

- Mark overloaded global directive in the subsequent definition ([#232](https://github.com/marp-team/marp-vscode/pull/232))
- `overloading-global-directive` diagnostic: Mark overloaded global directive in the subsequent definition ([#232](https://github.com/marp-team/marp-vscode/pull/232))
- `unknown-theme` diagnostic: Mark if the specified theme name is not recognized by the extension ([#236](https://github.com/marp-team/marp-vscode/pull/236))

### Changed

Expand Down
3 changes: 3 additions & 0 deletions src/diagnostics/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { window, TextDocument } from 'vscode'
import { DirectiveParser } from '../directive-parser'
import * as deprecatedDollarPrefix from './deprecated-dollar-prefix'
import * as overloadingGlobalDirective from './overloading-global-directive'
import * as unknownTheme from './unknown-theme'
import * as diagnostics from './index'

jest.mock('lodash.debounce')
jest.mock('vscode')
jest.mock('./deprecated-dollar-prefix')
jest.mock('./overloading-global-directive')
jest.mock('./unknown-theme')

const plainTextDocMock: TextDocument = {
languageId: 'plaintext',
Expand Down Expand Up @@ -81,6 +83,7 @@ describe('Diagnostics', () => {
parser,
arr
)
expect(unknownTheme.register).toHaveBeenCalledWith(doc, parser, arr)
})
})
})
2 changes: 2 additions & 0 deletions src/diagnostics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { DirectiveParser } from '../directive-parser'
import { detectMarpDocument } from '../utils'
import * as deprecatedDollarPrefix from './deprecated-dollar-prefix'
import * as overloadingGlobalDirective from './overloading-global-directive'
import * as unknownTheme from './unknown-theme'

export const collection = languages.createDiagnosticCollection('marp-vscode')

Expand All @@ -20,6 +21,7 @@ const setDiagnostics = lodashDebounce((doc: TextDocument) => {

deprecatedDollarPrefix.register(doc, directiveParser, diagnostics)
overloadingGlobalDirective.register(doc, directiveParser, diagnostics)
unknownTheme.register(doc, directiveParser, diagnostics)

directiveParser.parse(doc)

Expand Down
84 changes: 84 additions & 0 deletions src/diagnostics/unknown-theme.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import dedent from 'dedent'
import {
Diagnostic,
DiagnosticSeverity,
Position,
Range,
TextDocument,
Uri,
} from 'vscode'
import { DirectiveParser } from '../directive-parser'
import { Themes } from '../themes'
import * as rule from './unknown-theme'

jest.mock('vscode')

const doc = (text: string): TextDocument =>
({
getText: () => text,
positionAt: (offset: number) => {
const lines = text.slice(0, offset).split('\n')

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return new Position(lines.length - 1, lines.pop()!.length)
},
uri: Uri.parse('/test/document'),
fileName: '/test/document',
} as any)

describe('[Diagnostics rule] Unknown theme', () => {
const register = (doc: TextDocument): Diagnostic[] => {
const parser = new DirectiveParser()
const diagnostics: Diagnostic[] = []

rule.register(doc, parser, diagnostics)

parser.parse(doc)
return diagnostics
}

describe('#register', () => {
it('adds diagnostics when passed theme directive with not recognized theme name', () => {
const diagnostics = register(
doc(dedent`
---
marp: true
theme: unknown
test: test
---
`)
)
expect(diagnostics).toHaveLength(1)

const [$theme] = diagnostics
expect($theme).toBeInstanceOf(Diagnostic)
expect($theme.code).toBe(rule.code)
expect($theme.source).toBe('marp-vscode')
expect($theme.severity).toBe(DiagnosticSeverity.Warning)
expect($theme.range).toStrictEqual(
new Range(new Position(2, 7), new Position(2, 14))
)
})

it('does not add diagnostics when theme directive is not defined', () =>
expect(register(doc(''))).toHaveLength(0))

it('does not add diagnostics when the specified theme is recognized', () => {
expect(register(doc('<!-- theme: default -->'))).toHaveLength(0)
expect(register(doc('<!-- theme: gaia -->'))).toHaveLength(0)
expect(register(doc('<!-- theme: uncover -->'))).toHaveLength(0)
})

describe('when registered custom theme', () => {
beforeEach(() => {
jest
.spyOn(Themes.prototype, 'getRegisteredStyles')
.mockReturnValue([{ css: '/* @theme custom-theme */' } as any])
})

it('does not add diagnostics when specified the name of custom theme', () => {
expect(register(doc('<!-- theme: custom-theme -->'))).toHaveLength(0)
})
})
})
})
55 changes: 55 additions & 0 deletions src/diagnostics/unknown-theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Diagnostic, DiagnosticSeverity, Range, TextDocument } from 'vscode'
import { DirectiveParser } from '../directive-parser'
import themes from '../themes'

interface ParsedThemeValue {
value: string
range: Range
}

export const code = 'unknown-theme'

export function register(
doc: TextDocument,
directiveParser: DirectiveParser,
diagnostics: Diagnostic[]
) {
let parsed: ParsedThemeValue | undefined

directiveParser.on('startParse', () => {
parsed = undefined
})

directiveParser.on('directive', ({ item, offset, info }) => {
if (info?.name === 'theme') {
const [start, end] = item.value.range

parsed = {
value: item.value.value,
range: new Range(
doc.positionAt(start + offset),
doc.positionAt(end + offset)
),
}
}
})

directiveParser.on('endParse', () => {
if (parsed) {
const themeSet = themes.getMarpThemeSetFor(doc)

if (!themeSet.has(parsed.value)) {
const diagnostic = new Diagnostic(
parsed.range,
`The specified theme "${parsed.value}" is not recognized by Marp for VS Code.`,
DiagnosticSeverity.Warning
)

diagnostic.source = 'marp-vscode'
diagnostic.code = code

diagnostics.push(diagnostic)
}
}
})
}
7 changes: 2 additions & 5 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { marpCoreOptionForPreview, clearMarpCoreOptionCache } from './option'
import customTheme from './plugins/custom-theme'
import lineNumber from './plugins/line-number'
import outline, { rule as outlineRule } from './plugins/outline'
import themes from './themes'
import themes, { Themes } from './themes'
import { detectMarpFromMarkdown, marpConfiguration } from './utils'

const shouldRefreshConfs = [
Expand Down Expand Up @@ -45,10 +45,7 @@ export function extendMarkdownIt(md: any) {
document.languageId === 'markdown' &&
document.getText().replace(/\u2028|\u2029/g, '') === markdown
) {
const workspaceFolder = workspace.getWorkspaceFolder(document.uri)
if (workspaceFolder) return workspaceFolder.uri

return document.uri.with({ path: path.dirname(document.fileName) })
return Themes.resolveBaseDirectoryForTheme(document)
}
}
return undefined
Expand Down
25 changes: 25 additions & 0 deletions src/themes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import fs from 'fs'
import path from 'path'
import { URL } from 'url'
import { promisify, TextDecoder } from 'util'
import Marp from '@marp-team/marp-core'
import axios from 'axios'
import {
commands,
workspace,
Disposable,
GlobPattern,
RelativePattern,
TextDocument,
Uri,
} from 'vscode'
import { marpConfiguration } from './utils'
Expand Down Expand Up @@ -40,6 +42,13 @@ const textDecoder = new TextDecoder()
export class Themes {
observedThemes = new Map<string, Theme>()

static resolveBaseDirectoryForTheme(doc: TextDocument): Uri {
const workspaceFolder = workspace.getWorkspaceFolder(doc.uri)
if (workspaceFolder) return workspaceFolder.uri

return doc.uri.with({ path: path.dirname(doc.fileName) })
}

dispose() {
this.observedThemes.forEach((theme) => {
if (theme.onDidChange) theme.onDidChange.dispose()
Expand All @@ -48,6 +57,22 @@ export class Themes {
this.observedThemes.clear()
}

getMarpThemeSetFor(doc: TextDocument) {
const marp = new Marp()

for (const { css } of this.getRegisteredStyles(
Themes.resolveBaseDirectoryForTheme(doc)
)) {
try {
marp.themeSet.add(css)
} catch (e) {
// no ops
}
}

return marp.themeSet
}

getRegisteredStyles(rootUri: Uri | undefined): Theme[] {
return this.getPathsFromConf(rootUri)
.map((p) => this.observedThemes.get(p))
Expand Down

0 comments on commit 882d7cb

Please sign in to comment.