jgranstrom/sass-extract

Freezing of the JavaScript process due to regexp matching

Closed this issue · 5 comments

I've stumbled upon a curious edge case while using this module. I'm attempting to parse out the variables from an SCSS file which imports an SCSS file that has utility functions defined. When parsing the entry file I found out that my node process started to randomly freeze without no explanation what so ever.

I managed to trace down the source of the issue to the regexp based parsing of the SCSS files. When it's trying to extract implicit variables from the file it ends up with the following snippet:

/(\$[\w-_]+)\s*:\s*((.*?\n?)+?);/g.exec('@function decimal-round ($number, $digits: 0, $mode: round) \n@function decimal-ceil ($number, $digits: 0) \n@function decimal-floor ($number, $digits: 0) \n\n@function strip-unit($number) \n')

If you execute the code above in the node REPL or even in your browser console it will completely freeze up.

// _decimal.scss | MIT License | gist.github.com/terkel/4373420
@function decimal-round ($number, $digits: 0, $mode: round) {
    $n: 1;
    // $number must be a number
    @if type-of($number) != number {
        @warn '#{ $number } is not a number.';
        @return $number;
    }
    // $digits must be a unitless number
    @if type-of($digits) != number {
        @warn '#{ $digits } is not a number.';
        @return $number;
    } @else if not unitless($digits) {
        @warn '#{ $digits } has a unit.';
        @return $number;
    }
    @for $i from 1 through $digits {
        $n: $n * 10;
    }
    @if $mode == round {
        @return round($number * $n) / $n;
    } @else if $mode == ceil {
        @return ceil($number * $n) / $n;
    } @else if $mode == floor {
        @return floor($number * $n) / $n;
    } @else {
        @warn '#{ $mode } is undefined keyword.';
        @return $number;
    }
}
@function decimal-ceil ($number, $digits: 0) {
    @return decimal-round($number, $digits, ceil);
}
@function decimal-floor ($number, $digits: 0) {
    @return decimal-round($number, $digits, floor);
}

//CSS-Tricks
@function strip-unit($number) {
  @if type-of($number) == 'number' and not unitless($number) {
    @return $number / ($number * 0 + 1);
  }
  @return $number;
}

Got a bunch of fixes for this said bug in my fork/branch (https://github.com/jgranstrom/sass-extract/compare/master...3rd-Eden:regexp?expand=1) will create a PR for this after I fixed the last set of bugs that I'm running in to.

Thank you for your efforts in investigating these issues!

I am in addition looking into a more robust parsing method that won't require regexp for reasons like this one. Patches are very welcome for the bugs you come across.

@jgranstrom Yeah, my branch has a couple of bug fixes already. As for alternate ways to parse there's https://github.com/postcss/postcss-scss which uses a tokenizer but that would require a lot refactoring and I don't know if that is worth the effort as the current regexp's work fine after making them less greedy.

Great stuff @3rd-Eden. I am looking into using scss-parser for parsing declarations from the AST of each file, which will be helpful in preparations for supporting local variables declared in specific scopes in addition to the global ones currently supported.

@3rd-Eden check out #8 It should fix your issue. It also prevents freezing in my case