1#![cfg_attr(doc_cfg, feature(doc_cfg))]
36#![warn(clippy::all, clippy::cargo, clippy::dbg_macro)]
37#![deny(missing_debug_implementations)]
38#![allow(
39 clippy::use_self,
40 clippy::manual_filter_map,
42 renamed_and_removed_lints,
43 clippy::unknown_clippy_lints,
44 clippy::single_match,
45 clippy::new_without_default,
46 clippy::single_match_else,
47 clippy::multiple_crate_versions,
48 clippy::wrong_self_convention,
49 clippy::comparison_chain,
50 clippy::unwrap_or_default,
51 clippy::manual_unwrap_or_default,
52
53 clippy::arc_with_non_send_sync,
55
56 clippy::assigning_clones,
58
59 unknown_lints,
60)]
61
62use std::path::Path;
63
64use parse::{CssParser, SassParser, StylesheetParser};
65use sass_ast::StyleSheet;
66use serializer::Serializer;
67#[cfg(feature = "wasm-exports")]
68use wasm_bindgen::prelude::*;
69
70use codemap::CodeMap;
71
72pub use crate::error::{
73 PublicSassErrorKind as ErrorKind, SassError as Error, SassResult as Result,
74};
75pub use crate::fs::{Fs, NullFs, StdFs};
76pub use crate::logger::{Logger, NullLogger, StdLogger};
77pub use crate::options::{InputSyntax, Options, OutputStyle};
78pub use crate::{builtin::Builtin, evaluate::Visitor};
79pub(crate) use crate::{context_flags::ContextFlags, lexer::Token};
80use crate::{lexer::Lexer, parse::ScssParser};
81
82pub mod sass_value {
83 pub use crate::{
84 ast::ArgumentResult,
85 color::Color,
86 common::{BinaryOp, Brackets, ListSeparator, QuoteKind},
87 unit::{ComplexUnit, Unit},
88 value::{
89 ArgList, CalculationArg, CalculationName, Number, SassCalculation, SassFunction,
90 SassMap, SassNumber, Value,
91 },
92 };
93}
94
95pub mod sass_ast {
96 pub use crate::ast::*;
97}
98
99pub use codemap;
100
101mod ast;
102mod builtin;
103mod color;
104mod common;
105mod context_flags;
106mod error;
107mod evaluate;
108mod fs;
109mod interner;
110mod lexer;
111mod logger;
112mod options;
113mod parse;
114mod selector;
115mod serializer;
116mod unit;
117mod utils;
118mod value;
119
120fn raw_to_parse_error(map: &CodeMap, err: Error, unicode: bool) -> Box<Error> {
121 let (message, span) = err.raw();
122 Box::new(Error::from_loc(message, map.look_up_span(span), unicode))
123}
124
125pub fn parse_stylesheet<P: AsRef<Path>>(
126 input: String,
127 file_name: P,
128 options: &Options,
129) -> Result<StyleSheet> {
130 let mut map = CodeMap::new();
132 let path = file_name.as_ref();
133 let file = map.add_file(path.to_string_lossy().into_owned(), input);
134 let empty_span = file.span.subspan(0, 0);
135 let lexer = Lexer::new_from_file(&file);
136
137 let input_syntax = options
138 .input_syntax
139 .unwrap_or_else(|| InputSyntax::for_path(path));
140
141 let stylesheet = match input_syntax {
142 InputSyntax::Scss => {
143 ScssParser::new(lexer, options, empty_span, file_name.as_ref()).__parse()
144 }
145 InputSyntax::Sass => {
146 SassParser::new(lexer, options, empty_span, file_name.as_ref()).__parse()
147 }
148 InputSyntax::Css => {
149 CssParser::new(lexer, options, empty_span, file_name.as_ref()).__parse()
150 }
151 };
152
153 let stylesheet = match stylesheet {
154 Ok(v) => v,
155 Err(e) => return Err(raw_to_parse_error(&map, *e, options.unicode_error_messages)),
156 };
157
158 Ok(stylesheet)
159}
160
161fn from_string_with_file_name<P: AsRef<Path>>(
162 input: String,
163 file_name: P,
164 options: &Options,
165) -> Result<String> {
166 let mut map = CodeMap::new();
167 let path = file_name.as_ref();
168 let file = map.add_file(path.to_string_lossy().into_owned(), input);
169 let empty_span = file.span.subspan(0, 0);
170 let lexer = Lexer::new_from_file(&file);
171
172 let input_syntax = options
173 .input_syntax
174 .unwrap_or_else(|| InputSyntax::for_path(path));
175
176 let stylesheet = match input_syntax {
177 InputSyntax::Scss => {
178 ScssParser::new(lexer, options, empty_span, file_name.as_ref()).__parse()
179 }
180 InputSyntax::Sass => {
181 SassParser::new(lexer, options, empty_span, file_name.as_ref()).__parse()
182 }
183 InputSyntax::Css => {
184 CssParser::new(lexer, options, empty_span, file_name.as_ref()).__parse()
185 }
186 };
187
188 let stylesheet = match stylesheet {
189 Ok(v) => v,
190 Err(e) => return Err(raw_to_parse_error(&map, *e, options.unicode_error_messages)),
191 };
192
193 let mut visitor = Visitor::new(path, options, &mut map, empty_span);
194 match visitor.visit_stylesheet(stylesheet) {
195 Ok(_) => {}
196 Err(e) => return Err(raw_to_parse_error(&map, *e, options.unicode_error_messages)),
197 }
198 let stmts = visitor.finish();
199
200 let mut serializer = Serializer::new(options, &map, false, empty_span);
201
202 let mut prev_was_group_end = false;
203 let mut prev_requires_semicolon = false;
204 for stmt in stmts {
205 if stmt.is_invisible() {
206 continue;
207 }
208
209 let is_group_end = stmt.is_group_end();
210 let requires_semicolon = Serializer::requires_semicolon(&stmt);
211
212 serializer
213 .visit_group(stmt, prev_was_group_end, prev_requires_semicolon)
214 .map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))?;
215
216 prev_was_group_end = is_group_end;
217 prev_requires_semicolon = requires_semicolon;
218 }
219
220 Ok(serializer.finish(prev_requires_semicolon))
221}
222
223#[inline]
235pub fn from_path<P: AsRef<Path>>(p: P, options: &Options) -> Result<String> {
236 from_string_with_file_name(String::from_utf8(options.fs.read(p.as_ref())?)?, p, options)
237}
238
239#[inline]
250pub fn from_string<S: Into<String>>(input: S, options: &Options) -> Result<String> {
251 from_string_with_file_name(input.into(), "stdin", options)
252}
253
254#[cfg(feature = "wasm-exports")]
255#[wasm_bindgen(js_name = from_string)]
256pub fn from_string_js(input: String) -> std::result::Result<String, String> {
257 from_string(input, &Options::default()).map_err(|e| e.to_string())
258}