or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.md

index.mddocs/

0

# PostCSS Safe Parser

1

2

PostCSS Safe Parser is a fault-tolerant CSS parser for PostCSS that can handle malformed or incomplete CSS syntax without throwing errors. It automatically finds and fixes syntax errors, making it capable of parsing any CSS input including legacy code with browser hacks and incomplete stylesheets.

3

4

## Package Information

5

6

- **Package Name**: postcss-safe-parser

7

- **Package Type**: npm

8

- **Language**: JavaScript

9

- **Installation**: `npm install postcss-safe-parser`

10

11

## Core Imports

12

13

```javascript

14

const safe = require('postcss-safe-parser');

15

```

16

17

ESM (if using a build tool that supports it):

18

19

```javascript

20

import safe from 'postcss-safe-parser';

21

```

22

23

## Basic Usage

24

25

```javascript

26

const postcss = require('postcss');

27

const safe = require('postcss-safe-parser');

28

29

// Parse malformed CSS that would break the standard parser

30

const badCss = 'a { color: black';

31

const result = await postcss().process(badCss, { parser: safe });

32

console.log(result.css); // 'a { color: black}'

33

34

// Direct parsing without PostCSS

35

const root = safe('a { /* unclosed comment');

36

console.log(root.toString()); // 'a { /* unclosed comment */}'

37

```

38

39

## Architecture

40

41

PostCSS Safe Parser extends PostCSS's core parser with fault-tolerant parsing capabilities. The architecture consists of:

42

43

- **SafeParser Class**: Extends PostCSS's `Parser` class, overriding key methods to handle malformed syntax

44

- **Error Recovery System**: Custom tokenizer with `ignoreErrors: true` that continues parsing despite syntax errors

45

- **Graceful Degradation**: Methods like `checkMissedSemicolon()`, `unclosedBracket()`, and `unexpectedClose()` are overridden to handle errors silently

46

- **PostCSS Integration**: Seamlessly integrates with PostCSS's processing pipeline as a drop-in parser replacement

47

48

The safe parser creates the same PostCSS AST structure as the standard parser, ensuring full compatibility with all PostCSS plugins and tools.

49

50

## Capabilities

51

52

### Safe CSS Parsing

53

54

Parses CSS with automatic error recovery and syntax fixing.

55

56

```javascript { .api }

57

/**

58

* Parse CSS string with fault tolerance

59

* @param css - CSS string to parse (can contain syntax errors)

60

* @param opts - Options object passed to PostCSS Input constructor

61

* @param opts.from - Input file path for source map generation

62

* @param opts.to - Output file path for source map generation

63

* @param opts.origin - Custom origin function for Input

64

* @param opts.map - Source map options or boolean

65

* @returns PostCSS Root AST node with fault-tolerant parsing applied

66

*/

67

function safeParse(css, opts = {});

68

```

69

70

**Parameters:**

71

72

- `css` (string): CSS input to parse - can contain malformed syntax, unclosed blocks, missing semicolons, etc.

73

- `opts` (object, optional): Options object passed to PostCSS Input constructor

74

- `from` (string): Input file path for source map generation

75

- `origin` (function): Custom origin function for Input

76

- Any other options supported by PostCSS Input

77

78

**Returns:**

79

80

PostCSS Root node containing the parsed CSS AST with all syntax errors automatically fixed.

81

82

**Error Recovery Features:**

83

84

The parser automatically handles and fixes:

85

86

- **Unclosed blocks**: `a {` becomes `a {}`

87

- **Unclosed comments**: `/* comment` becomes `/* comment */`

88

- **Missing semicolons**: `a { color: red font-size: 12px }` becomes `a { color: red; font-size: 12px }`

89

- **Unclosed quotes**: `content: "text` becomes `content: "text"`

90

- **Unclosed brackets**: `:not(input` becomes `:not(input)`

91

- **Properties without values**: `a { color; }` preserves structure

92

- **Nameless at-rules**: `@` becomes valid at-rule node

93

- **Double colons**: `a { prop:: value }` becomes `a { prop: : value }`

94

- **Complex nested JSON-like properties**: Handles CSS custom properties with JSON values

95

96

**Usage Examples:**

97

98

```javascript

99

// Parse CSS with unclosed blocks

100

const root = safe('@media (screen) { a {\n');

101

console.log(root.toString()); // '@media (screen) { a {\n}}'

102

103

// Parse CSS with missing semicolons

104

const root = safe('a { color: red font-size: 12px }');

105

console.log(root.toString()); // 'a { color: red; font-size: 12px }'

106

107

// Parse CSS with unclosed comments

108

const root = safe('a { /* comment ');

109

console.log(root.toString()); // 'a { /* comment */}'

110

111

// Parse CSS with complex JSON-like custom properties

112

const root = safe(':root { --config: {"nested": {"key": "value"}}; }');

113

console.log(root.toString()); // Preserves the complex structure

114

```

115

116

### Integration with PostCSS

117

118

Use as a parser option in PostCSS processing pipelines.

119

120

```javascript { .api }

121

// Standard PostCSS integration

122

postcss(plugins).process(css, { parser: safeParse })

123

124

// With async/await

125

const result = await postcss([autoprefixer]).process(badCss, {

126

parser: safe,

127

from: 'input.css'

128

});

129

```

130

131

**Common Integration Patterns:**

132

133

```javascript

134

const postcss = require('postcss');

135

const autoprefixer = require('autoprefixer');

136

const safe = require('postcss-safe-parser');

137

138

// Live CSS editing tools - parse as user types

139

async function parseUserInput(userCss) {

140

try {

141

const result = await postcss([autoprefixer])

142

.process(userCss, { parser: safe });

143

return result.css;

144

} catch (error) {

145

// Safe parser won't throw, but plugins might

146

return userCss;

147

}

148

}

149

150

// Legacy CSS processing

151

async function processLegacyCSS(legacyCss) {

152

const result = await postcss([

153

// Your plugins here

154

]).process(legacyCss, {

155

parser: safe,

156

from: 'legacy.css'

157

});

158

return result;

159

}

160

161

// Browser hacks and malformed CSS

162

const browserHacksCSS = `

163

.selector {

164

property: value\\9; /* IE hack */

165

*property: value; /* IE6/7 hack */

166

_property: value /* IE6 hack

167

`;

168

169

const cleaned = safe(browserHacksCSS);

170

console.log(cleaned.toString()); // Properly closed and parsed

171

```

172

173

### SafeParser Class

174

175

Direct access to the SafeParser class for advanced usage scenarios.

176

177

```javascript { .api }

178

const SafeParser = require('postcss-safe-parser/lib/safe-parser');

179

const { Input } = require('postcss');

180

181

/**

182

* SafeParser class extending PostCSS Parser

183

* Provides fault-tolerant CSS parsing with error recovery

184

*/

185

class SafeParser extends Parser {

186

constructor(input);

187

parse(): void;

188

createTokenizer(): void;

189

comment(token): void;

190

decl(tokens): void;

191

endFile(): void;

192

precheckMissedSemicolon(tokens): void;

193

unclosedBracket(): void;

194

unexpectedClose(): void;

195

unknownWord(tokens): void;

196

unnamedAtrule(node): void;

197

}

198

```

199

200

**Direct SafeParser Usage:**

201

202

```javascript

203

const { Input } = require('postcss');

204

const SafeParser = require('postcss-safe-parser/lib/safe-parser');

205

206

// Create parser instance directly

207

const input = new Input(cssString, { from: 'input.css' });

208

const parser = new SafeParser(input);

209

parser.parse();

210

211

// Access the parsed root

212

const root = parser.root;

213

console.log(root.toString());

214

```

215

216

**Key Method Behaviors:**

217

218

- `comment(token)`: Handles unclosed comments by automatically closing them

219

- `decl(tokens)`: Validates declaration tokens before processing

220

- `endFile()`: Ensures proper file closure even with unclosed blocks

221

- `unclosedBracket()`: Silently handles unclosed brackets without throwing

222

- `unexpectedClose()`: Handles extra closing braces by adding them to `raws.after`

223

- `unknownWord(tokens)`: Treats unknown tokens as whitespace instead of throwing errors

224

225

**Advanced Usage Patterns:**

226

227

```javascript

228

// Custom input processing with source maps

229

const { Input } = require('postcss');

230

const SafeParser = require('postcss-safe-parser/lib/safe-parser');

231

232

function parseWithCustomInput(css, filename) {

233

const input = new Input(css, {

234

from: filename,

235

origin: (offset, file) => {

236

// Custom source mapping logic

237

const lines = css.substring(0, offset).split('\n');

238

return { line: lines.length, col: lines[lines.length - 1].length + 1 };

239

}

240

});

241

242

const parser = new SafeParser(input);

243

parser.parse();

244

return parser.root;

245

}

246

247

// Parser with custom error recovery tracking

248

class TrackedSafeParser extends SafeParser {

249

constructor(input) {

250

super(input);

251

this.recoveredErrors = [];

252

}

253

254

unexpectedClose() {

255

super.unexpectedClose();

256

this.recoveredErrors.push({

257

type: 'unexpected_close',

258

position: this.tokenizer.position()

259

});

260

}

261

262

unclosedBracket() {

263

super.unclosedBracket();

264

this.recoveredErrors.push({

265

type: 'unclosed_bracket',

266

position: this.tokenizer.position()

267

});

268

}

269

}

270

```

271

272

## Error Handling

273

274

The safe parser is designed to never throw parsing errors. It will always return a valid PostCSS AST, regardless of the input CSS quality. However, downstream PostCSS plugins may still throw errors during processing.

275

276

```javascript

277

// Safe parser never throws

278

const root = safe('completely { malformed css {{{'); // Always succeeds

279

280

// But plugins might throw during processing

281

try {

282

const result = await postcss([somePlugin])

283

.process(malformedCss, { parser: safe });

284

} catch (pluginError) {

285

// Handle plugin errors, not parser errors

286

}

287

```

288

289

## Types

290

291

For TypeScript projects, the parser follows PostCSS type definitions:

292

293

```typescript { .api }

294

import { Root, Input, Parser, Node, Comment, Rule, AtRule, Declaration } from 'postcss';

295

296

/**

297

* Main safe parsing function

298

* @param css - CSS string to parse with fault tolerance

299

* @param opts - Options passed to PostCSS Input constructor

300

* @returns PostCSS Root AST node

301

*/

302

declare function safeParse(css: string, opts?: ProcessOptions): Root;

303

304

interface ProcessOptions {

305

from?: string;

306

to?: string;

307

origin?: (offset: number, file?: string) => { line: number; col: number };

308

map?: SourceMapOptions | boolean;

309

}

310

311

interface SourceMapOptions {

312

inline?: boolean;

313

prev?: string | object | boolean;

314

sourcesContent?: boolean;

315

annotation?: boolean | string;

316

from?: string;

317

to?: string;

318

}

319

320

/**

321

* SafeParser class extending PostCSS Parser

322

*/

323

declare class SafeParser extends Parser {

324

constructor(input: Input);

325

326

parse(): void;

327

createTokenizer(): void;

328

comment(token: [string, string, number, number]): void;

329

decl(tokens: Array<[string, string, number, number]>): void;

330

endFile(): void;

331

precheckMissedSemicolon(tokens: Array<[string, string, number, number]>): void;

332

unclosedBracket(): void;

333

unexpectedClose(): void;

334

unknownWord(tokens: Array<[string, string, number, number]>): void;

335

unnamedAtrule(node: AtRule): void;

336

337

// Inherited from Parser

338

root: Root;

339

input: Input;

340

current: Node;

341

spaces: string;

342

semicolon: boolean;

343

customProperty: boolean;

344

}

345

346

interface Root {

347

type: 'root';

348

nodes: Node[];

349

source?: {

350

input: Input;

351

start?: { line: number; column: number; offset: number };

352

end?: { line: number; column: number; offset: number };

353

};

354

raws: {

355

before?: string;

356

after?: string;

357

semicolon?: boolean;

358

};

359

360

// Methods

361

toString(stringifier?: any): string;

362

toResult(opts?: any): any;

363

append(...nodes: Node[]): Root;

364

prepend(...nodes: Node[]): Root;

365

insertAfter(exist: Node, add: Node): Root;

366

insertBefore(exist: Node, add: Node): Root;

367

removeAll(): Root;

368

removeChild(child: Node): Root;

369

each(callback: (node: Node, index: number) => void | false): void;

370

walk(callback: (node: Node, index: number) => void | false): void;

371

walkRules(callback: (rule: Rule, index: number) => void | false): void;

372

walkAtRules(callback: (atrule: AtRule, index: number) => void | false): void;

373

walkComments(callback: (comment: Comment, index: number) => void | false): void;

374

walkDecls(callback: (decl: Declaration, index: number) => void | false): void;

375

}

376

```