diff --git a/internal/sass/_string-ext.scss b/internal/sass/_string-ext.scss deleted file mode 100644 index 0f891446b4..0000000000 --- a/internal/sass/_string-ext.scss +++ /dev/null @@ -1,195 +0,0 @@ -// -// Copyright 2021 Google LLC -// SPDX-License-Identifier: Apache-2.0 -// - -// go/keep-sorted start -@use 'sass:list'; -@use 'sass:string'; -// go/keep-sorted end - -/// Checks if a string starts with a given prefix. -/// -/// @example - scss -/// @debug has-prefix('var(--foo)', 'var('); // true -/// -/// @param {String} $string - The string to test. -/// @param {String} $prefix - The prefix to check. -/// @return {Boolean} True if the string starts with the given prefix. -@function has-prefix($string, $prefix) { - @return string.slice($string, 1, string.length($prefix)) == $prefix; -} - -/// Checks if a string ends with a given suffix. -/// -/// @example - scss -/// @debug has-suffix('var(--foo)', ')'); // true -/// -/// @param {String} $string - The string to test. -/// @param {String} $suffix - The suffix to check. -/// @return {Boolean} True if the string ends with the given suffix. -@function has-suffix($string, $suffix) { - @return string.slice($string, -1 * string.length($suffix)) == $suffix; -} - -/// Trims a repeating prefix from the start of a string. -/// -/// @example - scss -/// @debug trim-repeating-prefix(' foo bar ', ' '); // "foo bar " -/// -/// @param {String} $string - The string to trim. -/// @param {String} $prefix - The repeating prefix string to trim. -/// @return {String} The string with the prefix trimmed from the start. -@function trim-repeating-prefix($string, $prefix) { - @while has-prefix($string, $prefix) { - $string: trim-prefix($string, $prefix); - } - - @return $string; -} - -/// Trims a prefix from the start of a string. -/// -/// @example - scss -/// @debug trim-prefix('var(--foo)', 'var('); // "--foo)" -/// -/// @param {String} $string - The string to trim. -/// @param {String} $prefix - The prefix string to trim. -/// @return {String} The string with the prefix trimmed from the start. -@function trim-prefix($string, $prefix) { - @if has-prefix($string, $prefix) { - $string: string.slice($string, string.length($prefix) + 1); - } - - @return $string; -} - -/// Trims a repeating suffix from the end of a string. -/// -/// @example - scss -/// @debug trim-repeating-suffix(' foo bar ', ' '); // " foo bar" -/// @debug trim-repeating-suffix('var(--foo)', ')'); // "var(--foo" -/// -/// @param {String} $string - The string to trim. -/// @param {String} $suffix - The repeating suffix string to trim. -/// @return {String} The string with the suffix trimmed from the end. -@function trim-repeating-suffix($string, $suffix) { - @while has-suffix($string, $suffix) { - $string: trim-suffix($string, $suffix); - } - - @return $string; -} - -/// Trims a suffix from the end of a string. -/// -/// @example - scss -/// @debug trim-suffix('var(--foo)', ')'); // "var(--foo" -/// -/// @param {String} $string - The string to trim. -/// @param {String} $suffix - The suffix string to trim. -/// @return {String} The string with the suffix trimmed from the end. -@function trim-suffix($string, $suffix) { - @if has-suffix($string, $suffix) { - $string: string.slice($string, 1, -1 * string.length($suffix) - 1); - } - - @return $string; -} - -/// Trims a repeating prefix and suffix from the start and end of a string. -/// -/// If a suffix is not provided, the prefix is used as the suffix to trim. -/// -/// @example - scss -/// @debug trim-repeating(' foo bar ', ' '); // "foo bar" -/// -/// @param {String} $string - The string to trim. -/// @param {String} $prefix - The repeating prefix string to trim. -/// @param {String} $suffix [$prefix] - The repeating suffix string to trim. -/// @return {String} The string with the prefix and suffix trimmed. -@function trim-repeating($string, $prefix, $suffix: $prefix) { - @return trim-repeating-prefix( - trim-repeating-suffix($string, $suffix), - $prefix - ); -} - -/// Trims a prefix and suffix from the start and end of a string. -/// -/// If a suffix is not provided, the prefix is used as the suffix to trim. -/// -/// @example - scss -/// @debug trim('var(--foo)', 'var(', ')'); // "--foo" -/// -/// @param {String} $string - The string to trim. -/// @param {String} $prefix - The prefix string to trim. -/// @param {String} $suffix [$prefix] - The suffix string to trim. -/// @return {String} The string with the prefix and suffix trimmed. -@function trim($string, $prefix, $suffix: $prefix) { - @return trim-prefix(trim-suffix($string, $suffix), $prefix); -} - -/// Returns a new string with the first match of a pattern replaced by a -/// replacement. -/// -/// @example - scss -/// @debug trim('foo bar baz', 'bar', 'quux'); // "foo quux baz" -/// -/// @param {String} $string - The string to be searched. -/// @param {String} $pattern - The pattern to search for. -/// @param {String} $replacement - The value to replace the pattern. -/// @return {String} The string with the first match of pattern replaced by the -/// replacement or the initial string itself. -@function replace($string, $pattern, $replacement) { - $pattern-index: string.index($string, $pattern); - @if not $pattern-index { - @return $string; - } - - $before: string.slice($string, 1, $pattern-index - 1); - $after: string.slice($string, string.length($pattern) + $pattern-index); - - @return $before + $replacement + $after; -} - -/// Divides a string into an ordered list of substrings. -/// -/// @example - scss -/// @debug split("1px 2px 3px 4px"); // (1px 2px 3px 4px) -/// @debug split("1px, 2px, 3px, 4px"); // (1px 2px 3px 4px) -/// @debug split("1px/2px/3px/4px"); // (1px 2px 3px 4px) -/// -/// @param {String} $str - The string to split -/// @return {List} The list of substrings -@function split($str) { - $list: (); - $item: ''; - // list separator precedence is comma, slash, space - $separator: ' '; - @if string.index($str, ',') { - $separator: ','; - } @else if string.index($str, '/') { - $separator: '/'; - } - @for $i from 1 through string.length($str) { - $chr: string.slice($str, $i, $i); - @if $chr == $separator { - @if $item != '' { - // remove surrounding whitespace - $item: string.unquote(trim($item, ' ')); - $list: list.append($list, $item); - } - $item: ''; - } @else { - $item: $item + $chr; - } - } - // append final item - @if $item != '' { - // remove surrounding whitespace - $item: string.unquote(trim($item, ' ')); - $list: list.append($list, $item); - } - @return $list; -} diff --git a/internal/sass/_var.scss b/internal/sass/_var.scss index 6787cfffdd..ce5ac36b7d 100644 --- a/internal/sass/_var.scss +++ b/internal/sass/_var.scss @@ -9,7 +9,7 @@ @use 'sass:string'; // go/keep-sorted end // go/keep-sorted start -@use './string-ext'; +@use '../../sass/ext/string_ext'; // go/keep-sorted end /// Creates a custom property `var()` string. @@ -51,7 +51,7 @@ /// @param {String} $name - The name of the custom property. /// @return {String} The full valid CSS custom property variable name. @function create-name($name) { - @if string-ext.has-prefix($name, '--') { + @if string_ext.starts-with($name, '--') { @return $name; } @@ -173,7 +173,8 @@ /// @return {Bool} True if the value is a custom property `var()` string, or /// false if not. @function _is-var-string($var) { - @return meta.type-of($var) == 'string' and string-ext.has-prefix($var, 'var('); + @return meta.type-of($var) == 'string' and + string_ext.starts-with($var, 'var('); } /// Parses a `var()` string into a Map with `name` and `fallback` keys. This @@ -197,14 +198,15 @@ } // Remove function name and parens - $var: string-ext.trim($var, 'var(', ')'); + $var: string_ext.replace-start($var, 'var(', ''); + $var: string_ext.replace-end($var, ')', ''); - $name: string-ext.trim-repeating($var, ' '); + $name: string_ext.trim($var); $fallback: null; $comma: string.index($var, ','); @if $comma != null { - $name: string-ext.trim-repeating(string.slice($var, 1, $comma - 1), ' '); - $fallback: string-ext.trim-repeating(string.slice($var, $comma + 1), ' '); + $name: string_ext.trim(string.slice($var, 1, $comma - 1)); + $fallback: string_ext.trim(string.slice($var, $comma + 1)); @if _is-var-string($fallback) { $fallback: _parse($fallback); @if $fallback == null { diff --git a/internal/sass/test/_string-ext.test.scss b/internal/sass/test/_string-ext.test.scss deleted file mode 100644 index 9c4947bfa6..0000000000 --- a/internal/sass/test/_string-ext.test.scss +++ /dev/null @@ -1,200 +0,0 @@ -// -// Copyright 2021 Google LLC -// SPDX-License-Identifier: Apache-2.0 -// - -// go/keep-sorted start -@use 'sass:list'; -@use 'sass:meta'; -@use 'true' as test; -// go/keep-sorted end -// go/keep-sorted start -@use '../string-ext'; -// go/keep-sorted end - -@include test.describe('string-ext') { - @include test.describe('has-prefix()') { - @include test.it('should return true if the string has the prefix') { - @include test.assert-true(string-ext.has-prefix('foo', 'f')); - } - - @include test.it( - 'should return false if the string does not have the prefix' - ) { - @include test.assert-false(string-ext.has-prefix('foo', 'b')); - } - } - - @include test.describe('has-suffix()') { - @include test.it('should return true if the string has the suffix') { - @include test.assert-true(string-ext.has-suffix('foo', 'o')); - } - - @include test.it( - 'should return false if the string does not have the suffix' - ) { - @include test.assert-false(string-ext.has-suffix('foo', 'b')); - } - } - - @include test.describe('trim-repeating-prefix()') { - @include test.it('should return the string with the prefix removed') { - @include test.assert-equal( - string-ext.trim-repeating-prefix('foo', 'fo'), - 'o' - ); - } - - @include test.it( - 'should return the string with a repeating prefix removed' - ) { - @include test.assert-equal( - string-ext.trim-repeating-prefix('babar', 'ba'), - 'r' - ); - } - - @include test.it('should do nothing if the prefix does not exist') { - @include test.assert-equal( - string-ext.trim-repeating-prefix('foo', 'bar'), - 'foo' - ); - } - } - - @include test.describe('trim-prefix()') { - @include test.it('should only trim the prefix once') { - @include test.assert-equal(string-ext.trim-prefix('babar', 'ba'), 'bar'); - } - } - - @include test.describe('trim-repeating-suffix()') { - @include test.it('should return the string with the suffix removed') { - @include test.assert-equal( - string-ext.trim-repeating-suffix('babar', 'bar'), - 'ba' - ); - } - - @include test.it( - 'should return the string with a repeating suffix removed' - ) { - @include test.assert-equal( - string-ext.trim-repeating-suffix('foo', 'o'), - 'f' - ); - } - - @include test.it('should do nothing if the suffix does not exist') { - @include test.assert-equal( - string-ext.trim-repeating-suffix('foo', 'bar'), - 'foo' - ); - } - } - - @include test.describe('trim-suffix()') { - @include test.it('should only trim the suffix once') { - @include test.assert-equal(string-ext.trim-suffix('foo', 'o'), 'fo'); - } - } - - @include test.describe('trim-repeating()') { - @include test.it('should trim the same repeating prefix and suffix') { - @include test.assert-equal( - string-ext.trim-repeating(' foo bar ', ' '), - 'foo bar' - ); - } - - @include test.it('should trim different repeating prefixes and suffixes') { - @include test.assert-equal( - string-ext.trim-repeating('fffoo barrr', $prefix: 'f', $suffix: 'r'), - 'oo ba' - ); - } - } - - @include test.describe('trim()') { - @include test.it('should trim the a single prefix and suffix') { - @include test.assert-equal( - string-ext.trim(' foo bar ', ' '), - ' foo bar ' - ); - } - - @include test.it('should trim different prefixes and suffixes') { - @include test.assert-equal( - string-ext.trim('fffoo barrr', $prefix: 'f', $suffix: 'r'), - 'ffoo barr' - ); - } - } - - @include test.describe('replace()') { - @include test.it('should replace a pattern') { - @include test.assert-equal( - string-ext.replace('foo bar baz', 'foo', 'quux'), - 'quux bar baz' - ); - - @include test.assert-equal( - string-ext.replace('foo bar baz', 'bar', 'quux'), - 'foo quux baz' - ); - - @include test.assert-equal( - string-ext.replace('foo bar baz', 'baz', 'quux'), - 'foo bar quux' - ); - } - - @include test.it('should return the string if pattern dont match') { - @include test.assert-equal( - string-ext.replace('foo bar baz', 'quux', 'womp'), - 'foo bar baz' - ); - } - - @include test.it('replaces only first match') { - @include test.assert-equal( - string-ext.replace('womp womp', 'womp', 'doo'), - 'doo womp' - ); - } - } - - @include test.describe('split()') { - @include test.it('should return a list') { - $result: string-ext.split('foo bar baz'); - @include test.assert-equal(meta.type-of($result), 'list'); - } - - @include test.it('should return ordered substrings') { - $result: string-ext.split('foo bar baz'); - $expected: (foo bar baz); - @include test.assert-equal(list.length($result), 3); - @include test.assert-equal($result, $expected); - } - - @include test.it('should handle comma separated lists') { - $result: string-ext.split('foo, bar, baz'); - $expected: (foo bar baz); - @include test.assert-equal(list.length($result), 3); - @include test.assert-equal($result, $expected); - } - - @include test.it('should handle slash separated lists') { - $result: string-ext.split('foo/ bar/ baz'); - $expected: (foo bar baz); - @include test.assert-equal(list.length($result), 3); - @include test.assert-equal($result, $expected); - } - - @include test.it('should one-item list for simple strings') { - $result: string-ext.split('foo'); - $expected: list.append((), foo); - @include test.assert-equal($result, $expected); - } - } -} diff --git a/sass/ext/_string_ext.scss b/sass/ext/_string_ext.scss new file mode 100644 index 0000000000..89e9a265ec --- /dev/null +++ b/sass/ext/_string_ext.scss @@ -0,0 +1,153 @@ +// +// Copyright 2021 Google LLC +// SPDX-License-Identifier: Apache-2.0 +// + +// Extensions to the go/sass:string built-in module. + +// go/keep-sorted start by_regex='(.+) prefix_order=sass: +@use 'sass:list'; +@use 'sass:string'; +// go/keep-sorted end + +/// Checks if a string starts with a given prefix. +/// +/// @example scss +/// @debug string_ext.starts-with('var(--foo)', 'var('); // true +/// +/// @param {string} $string - The string to test. +/// @param {string} $prefix - The prefix to check. +/// @return {boolean} True if the string starts with the given prefix. +@function starts-with($string, $prefix) { + @return string.slice($string, 1, string.length($prefix)) == $prefix; +} + +/// Checks if a string ends with a given suffix. +/// +/// @example scss +/// @debug string_ext.ends-with('var(--foo)', ')'); // true +/// +/// @param {string} $string - The string to test. +/// @param {string} $suffix - The suffix to check. +/// @return {boolean} True if the string ends with the given suffix. +@function ends-with($string, $suffix) { + @return string.slice($string, -1 * string.length($suffix)) == $suffix; +} + +/// Trims leading whitespace from the start of a string. +/// +/// @example scss +/// @debug string_ext.trim-start(' foo bar '); // "foo bar " +/// +/// @param {string} $string - The string to trim. +/// @return {string} The string with whitespace trimmed from the start. +@function trim-start($string) { + @while starts-with($string, ' ') { + $string: replace-start($string, ' ', ''); + } + + @return $string; +} + +/// Trims trailing whitespace from the end of a string. +/// +/// @example scss +/// @debug string_ext.trim-end(' foo bar '); // " foo bar" +/// +/// @param {string} $string - The string to trim. +/// @return {string} The string with trailing whitespace trimmed from the end. +@function trim-end($string) { + @while ends-with($string, ' ') { + $string: replace-end($string, ' ', ''); + } + + @return $string; +} + +/// Trims leading and trailing whitespace from a string. +/// +/// @example scss +/// @debug string_ext.trim(' foo bar '); // "foo bar" +/// +/// @param {string} $string - The string to trim. +/// @return {string} The string with leading and trailing whitespace trimmed. +@function trim($string) { + @return trim-start(trim-end($string)); +} + +/// Returns a new string with the first match of a pattern replaced by a given +/// string. +/// +/// @example scss +/// @debug string_ext.replace('foo bar baz', 'bar', 'quux'); // "foo quux baz" +/// +/// @param {string} $string - The string to be searched. +/// @param {string} $pattern - The pattern to search for. +/// @param {string} $replacement - The value to replace the pattern. +/// @return {string} The string with the first match of pattern replaced by the +/// replacement or the initial string itself. +@function replace($string, $pattern, $replacement) { + $pattern-index: string.index($string, $pattern); + @if not $pattern-index { + @return $string; + } + + $before: string.slice($string, 1, $pattern-index - 1); + $after: string.slice($string, string.length($pattern) + $pattern-index); + + @return $before + $replacement + $after; +} + +/// Returns a new string with all matches of a pattern replaced by a given +/// string. +/// +/// @example scss +/// @debug string_ext.replace-all('foo bar baz', 'ba', 'qua'); // "foo quar quaz" +/// +/// @param {string} $string - The string to be searched. +/// @param {string} $pattern - The pattern to search for. +/// @param {string} $replacement - The value to replace the pattern. +/// @return {string} The string with all matches of pattern replaced by the +/// replacement or the initial string itself. +@function replace-all($string, $pattern, $replacement) { + @while string.index($string, $pattern) { + $string: replace($string, $pattern, $replacement); + } + + @return $string; +} + +/// Returns a new string that replaces a prefix at the start of the string. +/// +/// @example scss +/// @debug string_ext.replace-start('var(--foo)', 'var(', ''); // "--foo)" +/// +/// @param {string} $string - The string to be searched. +/// @param {string} $prefix - The prefix string to replace. +/// @param {string} $replacement - The value to replace the prefix. +/// @return {string} The string with the prefix replaced. +@function replace-start($string, $prefix, $replacement) { + @if starts-with($string, $prefix) { + $string: $replacement + string.slice($string, string.length($prefix) + 1); + } + + @return $string; +} + +/// Returns a new string that replaces a suffix at the end of the string. +/// +/// @example scss +/// @debug string_ext.replace-end('var(--foo)', ')', ''); // "var(--foo" +/// +/// @param {string} $string - The string to be searched. +/// @param {string} $suffix - The suffix string to replace. +/// @param {string} $replacement - The value to replace the suffix. +/// @return {string} The string with the suffix trimmed from the end. +@function replace-end($string, $suffix, $replacement) { + @if ends-with($string, $suffix) { + $string: string.slice($string, 1, -1 * string.length($suffix) - 1) + + $replacement; + } + + @return $string; +} diff --git a/sass/ext/_string_ext_test.scss b/sass/ext/_string_ext_test.scss new file mode 100644 index 0000000000..6de00d60bb --- /dev/null +++ b/sass/ext/_string_ext_test.scss @@ -0,0 +1,166 @@ +// +// Copyright 2021 Google LLC +// SPDX-License-Identifier: Apache-2.0 +// + +@use 'true' as test; + +// go/keep-sorted start by_regex='(.+) prefix_order=sass: +@use 'sass:list'; +@use 'sass:meta'; +@use 'string_ext'; +// go/keep-sorted end + +@include test.describe('string_ext') { + @include test.describe('starts-with()') { + @include test.it('should return true if the string has the prefix') { + @include test.assert-true(string_ext.starts-with('foo', 'f')); + } + + @include test.it( + 'should return false if the string does not have the prefix' + ) { + @include test.assert-false(string_ext.starts-with('foo', 'b')); + } + } + + @include test.describe('ends-with()') { + @include test.it('should return true if the string has the suffix') { + @include test.assert-true(string_ext.ends-with('foo', 'o')); + } + + @include test.it( + 'should return false if the string does not have the suffix' + ) { + @include test.assert-false(string_ext.ends-with('foo', 'b')); + } + } + + @include test.describe('trim-start()') { + @include test.it( + 'should return the string with leading whitespace removed' + ) { + @include test.assert-equal(string_ext.trim-start(' foo'), 'foo'); + } + + @include test.it('should do nothing if there is no leading whitespace') { + @include test.assert-equal(string_ext.trim-start('foo'), 'foo'); + } + } + + @include test.describe('trim-end()') { + @include test.it( + 'should return the string with trailing whitespace removed' + ) { + @include test.assert-equal(string_ext.trim-end('bar '), 'bar'); + } + + @include test.it('should do nothing if there is no trailing whitespace') { + @include test.assert-equal(string_ext.trim-end('bar'), 'bar'); + } + } + + @include test.describe('trim()') { + @include test.it('should trim leading and trailing whitespace') { + @include test.assert-equal(string_ext.trim(' foo bar '), 'foo bar'); + } + } + + @include test.describe('replace()') { + @include test.it('should replace a pattern') { + @include test.assert-equal( + string_ext.replace('foo bar baz', 'foo', 'quux'), + 'quux bar baz' + ); + + @include test.assert-equal( + string_ext.replace('foo bar baz', 'bar', 'quux'), + 'foo quux baz' + ); + + @include test.assert-equal( + string_ext.replace('foo bar baz', 'baz', 'quux'), + 'foo bar quux' + ); + } + + @include test.it('should return the string if pattern does not match') { + @include test.assert-equal( + string_ext.replace('foo bar baz', 'quux', 'womp'), + 'foo bar baz' + ); + } + + @include test.it('replaces only first match') { + @include test.assert-equal( + string_ext.replace('womp womp', 'womp', 'doo'), + 'doo womp' + ); + } + } + + @include test.describe('replace-all()') { + @include test.it('should replace a single pattern') { + @include test.assert-equal( + string_ext.replace-all('foo bar baz', 'foo', 'quux'), + 'quux bar baz' + ); + + @include test.assert-equal( + string_ext.replace-all('foo bar baz', 'bar', 'quux'), + 'foo quux baz' + ); + + @include test.assert-equal( + string_ext.replace-all('foo bar baz', 'baz', 'quux'), + 'foo bar quux' + ); + } + + @include test.it('should return the string if pattern does not match') { + @include test.assert-equal( + string_ext.replace-all('foo bar baz', 'quux', 'womp'), + 'foo bar baz' + ); + } + + @include test.it('replaces multiple matches') { + @include test.assert-equal( + string_ext.replace-all('womp womp', 'womp', 'doo'), + 'doo doo' + ); + } + } + + @include test.describe('replace-start()') { + @include test.it('should replace the prefix of a string') { + @include test.assert-equal( + string_ext.replace-start('babar', 'ba', 'foo'), + 'foobar' + ); + } + + @include test.it('should return the string if prefix does not match') { + @include test.assert-equal( + string_ext.replace-start('foo', 'bar', 'baz'), + 'foo' + ); + } + } + + @include test.describe('replace-end()') { + @include test.it('should replace the suffix of a string') { + @include test.assert-equal( + string_ext.replace-end('foobar', 'bar', 'foo'), + 'foofoo' + ); + } + + @include test.it('should return the string if suffix does not match') { + @include test.assert-equal( + string_ext.replace-end('foo', 'bar', 'baz'), + 'foo' + ); + } + } +} diff --git a/internal/sass/test/test.scss b/sass/ext/tests.scss similarity index 61% rename from internal/sass/test/test.scss rename to sass/ext/tests.scss index ce438ede06..ea754dccf1 100644 --- a/internal/sass/test/test.scss +++ b/sass/ext/tests.scss @@ -3,6 +3,10 @@ // SPDX-License-Identifier: Apache-2.0 // +@use 'true' as test with ( + $catch-errors: true +); + // go/keep-sorted start -@use './string-ext.test'; +@use 'string_ext_test'; // go/keep-sorted end diff --git a/tabs/internal/_tab.scss b/tabs/internal/_tab.scss index 50493d823f..463c80871e 100644 --- a/tabs/internal/_tab.scss +++ b/tabs/internal/_tab.scss @@ -10,7 +10,6 @@ // go/keep-sorted start @use '../../elevation/elevation'; @use '../../focus/focus-ring'; -@use '../../internal/sass/string-ext'; @use '../../ripple/ripple'; @use '../../tokens'; // go/keep-sorted end