Skip to content

Programmatic API

Sassy exposes its core classes for programmatic use. As of v5, the API uses a builder pattern for Theme and provides standalone engine classes (Lint, Resolve, Proof) that work without CLI infrastructure.

Terminal window
npm install @gesslar/sassy
SpecifierPurpose
@gesslar/sassyProgrammatic API (all exported classes)
@gesslar/sassy (bin)CLI entry point (sassy command)
import {FileObject, DirectoryObject} from '@gesslar/toolkit'
import {Theme} from '@gesslar/sassy'
const cwd = DirectoryObject.fromCwd()
const file = cwd.getFile('my-theme.yaml')
const theme = new Theme()
.setCwd(cwd)
.setThemeFile(file)
.setOptions({outputDir: './dist'})
await theme.load()
await theme.build()
const output = theme.getOutput() // compiled theme object
await theme.write() // write to disk

Engine classes are the preferred API surface for programmatic consumers. They have no CLI dependencies — give them a Theme and they return structured data. Engines automatically load and build as needed.

ExportDescription
ThemeTheme lifecycle: load, build, write, dependency tracking
LintLint engine — static analysis, returns structured issue data
ResolveResolve engine — token/scope resolution with trails
ProofProof engine — composed document view (pre-evaluation)
ColourColour manipulation utilities (lighten, darken, mix, etc.)
YamlSourceYAML source tracking — maps compiled output back to source locations

Theme uses a chainable builder. All setters return this.

const theme = new Theme()
.setCwd(cwd) // DirectoryObject
.setThemeFile(file) // FileObject
.setOptions({outputDir: './dist'}) // compilation options
.setCache(cache) // optional Cache instance
Builder MethodTypeDescription
setCwd(dir)DirectoryObjectWorking directory for relative path resolution
setThemeFile(file)FileObjectSource theme file (also derives theme name)
setOptions(opts)objectCompilation options (outputDir, dryRun, silent, nerd)
setCache(cache)CacheFile cache instance (optional — load() falls back to direct file read)
MethodReturnsDescription
load()Promise<this>Parse and validate the source file
build()Promise<this>Run the full compilation pipeline
write(force?)Promise<{status, file}>Write output to disk. Skips if hash unchanged unless force is true.
wouldWrite()Promise<boolean>Check whether the compiled output differs from the existing file on disk.
reset()voidClear compiled state for rebuild
getOutput()object | nullGet the compiled theme object
getPool()ThemePool | nullGet the variable resolution pool
getName()stringGet the theme name (derived from filename)
getSource()object | nullGet the parsed source data
getDependencies()SetGet tracked file dependencies
addDependency(file, source)thisTrack an import dependency
getSourceSection(path)unknownGet a section of the parsed source by dot-path (e.g. "theme.colors")
getProof(asObject?)string | object | nullGet the cached proof. Returns YAML string by default, object when asObject is true.
hasProof()booleanTrue when a cached proof exists
hasOutput()booleanTrue after build() — compiled output exists
hasSource()booleanTrue after load() — parsed source exists
canBuild()booleanTrue when source is loaded and build can proceed
canWrite()booleanTrue when compiled output is ready for writing
isCompiled()booleanTrue when output, pool, and lookup are all present
isValid()booleanTrue when source file and name are set
findSourceLocation(path)string | nullLook up the source file, line, and column for a dot-path in the compiled theme, formatted as file:line:col

The Lint class analyses a compiled theme and returns structured issue data. No CLI infrastructure needed.

import {DirectoryObject} from '@gesslar/toolkit'
import {Theme, Lint} from '@gesslar/sassy'
const cwd = DirectoryObject.fromCwd()
const file = cwd.getFile('my-theme.yaml')
const theme = new Theme()
.setCwd(cwd)
.setThemeFile(file)
.setOptions({})
// No manual load()/build() needed — the engine handles it
const results = await new Lint().run(theme)

Lint.run() returns an object with four arrays, one per section:

KeyContents
tokenColorsIssues from tokenColors rules
semanticTokenColorsIssues from semanticTokenColors rules
colorsIssues from colours rules
variablesUnused variable issues

Every issue object has these common fields:

FieldTypeDescription
typestringIssue type identifier (e.g. "duplicate-scope", "invalid-selector")
severitystringOne of "high", "medium", or "low"
messagestringHuman-readable description of the problem
locationstring | undefinedSource location as file:line:col (present when YAML source tracking is available)

Beyond these, each issue type adds fields specific to the problem it describes. For example, duplicate-scope includes an occurrences array, precedence-issue includes specificScope and broadScope, and unused-variable includes the variable name. The type field is the discriminator — use it to determine which additional fields are present.

Core rules (from Lint.ISSUE_TYPES):

TypeSeveritySectionDescription
duplicate-scopemediumtokenColorsSame scope in multiple entries
undefined-variablehighanyReference to a variable that does not exist
unused-variablelowvariablesDefined variable never referenced
precedence-issuehigh/lowtokenColorsBroad scope masks a more specific scope

tokenColors value/structure rules:

TypeSeverityDescription
tc-missing-settingshighEntry has no settings object
tc-empty-settingslowSettings object is empty
tc-invalid-hex-colourhighForeground/background is not a valid hex colour
tc-invalid-fontstylemediumUnknown fontStyle keyword
tc-invalid-valuehighProperty value has wrong type
tc-deprecated-backgroundmediumbackground property has limited support
tc-unknown-settings-propertylowUnrecognised property in settings
tc-multiple-global-defaultsmediumMultiple scopeless entries (only last applies)

semanticTokenColors rules:

TypeSeverityDescription
invalid-selectorhighSelector doesn’t match VS Code’s pattern
unrecognised-token-typelowToken type not in the standard set
unrecognised-modifierlowModifier not in the standard set
deprecated-token-typemediumToken type has a recommended replacement
duplicate-selectormediumEquivalent selector already defined
invalid-hex-colourhighColour value is not valid hex
invalid-fontstylemediumUnknown fontStyle keyword
invalid-valuehighValue has wrong type
fontstyle-conflictmediumfontStyle and boolean style properties both set
deprecated-propertymediumProperty is deprecated and non-functional
empty-rulelowStyle object is empty
missing-semantic-highlightinghighRules defined but semanticHighlighting not enabled
shadowed-rulelowMore specific selector fully shadows this one

See Lint Rules for detailed explanations and fix suggestions for each rule.

Issue types, severity levels, and section names are available as static properties:

Lint.SECTIONS.TOKEN_COLORS // "tokenColors"
Lint.SECTIONS.SEMANTIC_TOKEN_COLORS // "semanticTokenColors"
Lint.SECTIONS.COLORS // "colors"
Lint.SECTIONS.VARS // "vars"
Lint.SEVERITY.HIGH // "high"
Lint.SEVERITY.MEDIUM // "medium"
Lint.SEVERITY.LOW // "low"
Lint.ISSUE_TYPES.DUPLICATE_SCOPE // "duplicate-scope"
Lint.ISSUE_TYPES.UNDEFINED_VARIABLE // "undefined-variable"
Lint.ISSUE_TYPES.UNUSED_VARIABLE // "unused-variable"
Lint.ISSUE_TYPES.PRECEDENCE_ISSUE // "precedence-issue"

The Proof class returns the fully composed theme document (post-import, pre-evaluation).

import {DirectoryObject} from '@gesslar/toolkit'
import {Theme, Proof} from '@gesslar/sassy'
const cwd = DirectoryObject.fromCwd()
const file = cwd.getFile('my-theme.yaml')
const theme = new Theme()
.setCwd(cwd)
.setThemeFile(file)
.setOptions({})
// No manual load() needed — the engine handles it
const composed = await new Proof().run(theme)
// composed.config - resolved config
// composed.palette - merged palette with séance inlined
// composed.vars - merged vars
// composed.theme.colors - merged colors
// composed.theme.tokenColors - appended tokenColors
// composed.theme.semanticTokenColors - merged semanticTokenColors
// The proof is cached on the theme after build() or proof().
// Subsequent calls return the cached result without recomposing.
// Use getDependencies() to access the import chain.

The Resolve class traces token resolution through the variable dependency chain.

import {DirectoryObject} from '@gesslar/toolkit'
import {Theme, Resolve} from '@gesslar/sassy'
const cwd = DirectoryObject.fromCwd()
const file = cwd.getFile('my-theme.yaml')
const theme = new Theme()
.setCwd(cwd)
.setThemeFile(file)
.setOptions({})
// No manual load()/build() needed — the engine handles it
const resolver = new Resolve()
// Resolve a colour variable
const colorResult = await resolver.color(theme, 'editor.background')
// Resolve a tokenColors scope
const tokenResult = await resolver.tokenColor(theme, 'keyword.control')
// Resolve a semanticTokenColors scope
const semanticResult = await resolver.semanticTokenColor(theme, 'variable')
ParameterTypeDescription
themeThemeA Theme instance (auto-loads and builds if needed)
optionsobjectExactly one of the keys below
Option KeyTypeDescription
colorstringA colour property key (e.g. editor.background)
tokenColorstringA tokenColors scope (e.g. keyword.control)
semanticTokenColorstringA semanticTokenColors scope (e.g. variable)

The three options are mutually exclusive — pass exactly one per call.

The method returns an object whose shape depends on the resolution type and outcome.

Colour resolution (color):

FieldTypeDescription
foundbooleanWhether the colour key exists in the theme
namestringThe requested colour key
resolutionstringFinal resolved hex value (when found)
trailarrayResolution steps, each with value, type, and depth

Scope resolution (tokenColor / semanticTokenColor):

FieldTypeDescription
foundbooleanWhether a matching scope was found
namestringThe requested scope
ambiguousbooleantrue when multiple entries match and disambiguation is needed
matchesarrayAvailable disambiguations (when ambiguous)
entryNamestringThe matched tokenColors entry name
resolutionstringFinal resolved hex value
resolvedViaobjectPresent when resolved through precedence fallback (scope, relation)
noForegroundbooleantrue when the matched entry has no foreground property
staticbooleantrue when the value is a static literal (no variable resolution)
trailarrayResolution steps, each with value, type, and depth

Both return shapes include a trail array. Each element is an object with three fields:

FieldTypeDescription
valuestringThe token value at this point in the chain
typestringClassification of the value (see below)
depthnumberNesting level in the dependency tree (0 = top)
TypeMeaningExample
variableA variable reference$(std.fg), $(palette.white)
expressionA colour function calllighten($(primary), 20), oklch(0.14 0 0)
literalA hex value that was authored directly in the source#4b8ebd, #f0e
normalisedA hex value expanded from shorthand to long form#ff00ee (from authored #f0e)
resolvedA hex value that was computed from a non-hex expression#72b5e6

The three hex types tell you exactly where a value came from: literal is the authored shorthand, normalised is its long-form expansion, and resolved is a value computed through function evaluation or variable chains. This means you can search your source for literal values and render swatches on normalised and resolved values without regex guesswork.